Deskripsi: - login input - login redirect sesuai dg akses - tampilan jika tidak ada data ttd pada setting desa - disable button pada list kategori pengaduan dg value id == lainnya - disable button aksi pada list role dg value id == developer - tidak menampilkan list data menu akses pada modal tambah dan edi role - tampilan list permission pada table role - order data permission yg telah terpilih sesuai dengan data json menu NO Issues
169 lines
3.4 KiB
TypeScript
169 lines
3.4 KiB
TypeScript
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
import { prisma } from '@/server/lib/prisma'
|
|
import { jwt as jwtPlugin, type JWTPayloadSpec } from '@elysiajs/jwt'
|
|
import Elysia, { t, type Cookie, type HTTPHeaders, type StatusMap } from 'elysia'
|
|
import { type ElysiaCookie } from 'elysia/cookies'
|
|
|
|
const secret = process.env.JWT_SECRET
|
|
if (!secret) {
|
|
throw new Error('Missing JWT_SECRET in environment variables')
|
|
}
|
|
|
|
const isProd = process.env.NODE_ENV === 'production'
|
|
const NINETY_YEARS = 60 * 60 * 24 * 365 * 90
|
|
|
|
type JWT = {
|
|
sign(data: Record<string, string | number> & JWTPayloadSpec): Promise<string>
|
|
verify(
|
|
jwt?: string
|
|
): Promise<false | (Record<string, string | number> & JWTPayloadSpec)>
|
|
}
|
|
|
|
type COOKIE = Record<string, Cookie<string | undefined>>
|
|
|
|
type SET = {
|
|
headers: HTTPHeaders
|
|
status?: number | keyof StatusMap
|
|
redirect?: string
|
|
cookie?: Record<string, ElysiaCookie>
|
|
}
|
|
|
|
async function issueToken({
|
|
jwt,
|
|
cookie,
|
|
userId,
|
|
role,
|
|
expiresAt,
|
|
}: {
|
|
jwt: JWT
|
|
cookie: COOKIE
|
|
userId: string
|
|
role: 'host' | 'user'
|
|
expiresAt: number
|
|
}) {
|
|
const token = await jwt.sign({
|
|
sub: userId,
|
|
aud: role,
|
|
exp: expiresAt,
|
|
})
|
|
|
|
cookie.token?.set({
|
|
value: token,
|
|
httpOnly: true,
|
|
secure: isProd, // aktifkan hanya di production (HTTPS)
|
|
sameSite: 'strict',
|
|
maxAge: NINETY_YEARS,
|
|
path: '/',
|
|
})
|
|
|
|
return token
|
|
}
|
|
|
|
async function login({
|
|
body,
|
|
cookie,
|
|
set,
|
|
jwt,
|
|
}: {
|
|
body: { email: string; password: string }
|
|
cookie: COOKIE
|
|
set: SET
|
|
jwt: JWT
|
|
}) {
|
|
try {
|
|
const { email, password } = body
|
|
|
|
const user = await prisma.user.findUnique({
|
|
where: { email },
|
|
select: {
|
|
id: true,
|
|
password: true,
|
|
Role: {
|
|
select: {
|
|
permissions: true
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
if (!user) {
|
|
set.status = 401
|
|
return { message: 'User not found' }
|
|
}
|
|
|
|
if (user.password !== password) {
|
|
set.status = 401
|
|
return { message: 'Invalid password' }
|
|
}
|
|
|
|
const rawPermissions = user.Role?.permissions;
|
|
|
|
const akses = Array.isArray(rawPermissions)
|
|
? rawPermissions[0]?.toString()
|
|
: undefined;
|
|
|
|
const token = await issueToken({
|
|
jwt,
|
|
cookie,
|
|
userId: user.id,
|
|
role: 'user',
|
|
expiresAt: Math.floor(Date.now() / 1000) + NINETY_YEARS,
|
|
})
|
|
return { token, akses }
|
|
} catch (error) {
|
|
console.error('Error logging in:', error)
|
|
return {
|
|
message: 'Login failed',
|
|
error:
|
|
error instanceof Error ? error.message : JSON.stringify(error ?? null),
|
|
}
|
|
}
|
|
}
|
|
|
|
const Auth = new Elysia({
|
|
prefix: '/auth',
|
|
tags: ["auth"],
|
|
})
|
|
.use(
|
|
jwtPlugin({
|
|
name: 'jwt',
|
|
secret,
|
|
})
|
|
)
|
|
.post(
|
|
'/login',
|
|
async ({ jwt, body, cookie, set }) => {
|
|
return await login({
|
|
jwt: jwt as JWT,
|
|
body,
|
|
cookie: cookie as any,
|
|
set: set as any,
|
|
})
|
|
},
|
|
{
|
|
body: t.Object({
|
|
email: t.String(),
|
|
password: t.String(),
|
|
}),
|
|
detail: {
|
|
summary: 'login',
|
|
description: 'Login with phone; auto-register if not found',
|
|
},
|
|
}
|
|
)
|
|
.delete(
|
|
'/logout',
|
|
({ cookie }) => {
|
|
cookie.token?.remove()
|
|
return { message: 'Logout successful' }
|
|
},
|
|
{
|
|
detail: {
|
|
summary: 'logout',
|
|
description: 'Logout (clear token cookie)',
|
|
|
|
},
|
|
}
|
|
)
|
|
|
|
export default Auth |