Files
kycnotme/web/src/lib/defineProtectedAction.ts
2025-05-25 10:07:02 +00:00

125 lines
3.7 KiB
TypeScript

import {
ActionError,
defineAction,
type ActionAccept,
type ActionAPIContext,
type ActionHandler,
} from 'astro:actions'
import type { MaybePromise } from 'astro/actions/runtime/utils.js'
import type { z } from 'astro/zod'
type SpecialUserPermission = 'admin' | 'moderator' | 'verified'
type Permission = SpecialUserPermission | 'guest' | 'not-spammer' | 'user'
type ActionAPIContextWithUser = ActionAPIContext & {
locals: {
user: NonNullable<ActionAPIContext['locals']['user']>
}
}
type ActionHandlerWithUser<TInputSchema, TOutput> = TInputSchema extends z.ZodType
? (input: z.infer<TInputSchema>, context: ActionAPIContextWithUser) => MaybePromise<TOutput>
: (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
input: any,
context: ActionAPIContextWithUser
) => MaybePromise<TOutput>
export function defineProtectedAction<
P extends Permission | SpecialUserPermission[],
TOutput,
TAccept extends ActionAccept | undefined = undefined,
TInputSchema extends z.ZodType | undefined = TAccept extends 'form' ? z.ZodType<FormData> : undefined,
>({
accept,
input: inputSchema,
handler,
permissions,
}: {
input?: TInputSchema
accept?: TAccept
handler: P extends 'guest'
? ActionHandler<TInputSchema, TOutput>
: ActionHandlerWithUser<TInputSchema, TOutput>
permissions: P
}) {
return defineAction({
accept,
input: inputSchema,
handler: ((input, context) => {
if (permissions === 'guest' || (Array.isArray(permissions) && permissions.length === 0)) {
return handler(
input,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-explicit-any
context as any
)
}
if (!context.locals.user) {
throw new ActionError({
code: 'UNAUTHORIZED',
message: 'You must be logged in to perform this action.',
})
}
if (permissions === 'not-spammer' && context.locals.user.spammer) {
throw new ActionError({
code: 'FORBIDDEN',
message: 'Spammer users are not allowed to perform this action.',
})
}
if (
(permissions === 'verified' || (Array.isArray(permissions) && permissions.includes('verified'))) &&
!context.locals.user.verified
) {
if (context.locals.user.spammer) {
throw new ActionError({
code: 'FORBIDDEN',
message: 'Spammer users are not allowed to perform this action.',
})
}
throw new ActionError({
code: 'FORBIDDEN',
message: 'Verified user privileges required.',
})
}
if (
(permissions === 'moderator' || (Array.isArray(permissions) && permissions.includes('moderator'))) &&
!context.locals.user.moderator
) {
if (context.locals.user.spammer) {
throw new ActionError({
code: 'FORBIDDEN',
message: 'Spammer users are not allowed to perform this action.',
})
}
throw new ActionError({
code: 'FORBIDDEN',
message: 'Moderator privileges required.',
})
}
if (
(permissions === 'admin' || (Array.isArray(permissions) && permissions.includes('admin'))) &&
!context.locals.user.admin
) {
if (context.locals.user.spammer) {
throw new ActionError({
code: 'FORBIDDEN',
message: 'Spammer users are not allowed to perform this action.',
})
}
throw new ActionError({
code: 'FORBIDDEN',
message: 'Admin privileges required.',
})
}
return handler(input, context as ActionAPIContextWithUser)
}) as ActionHandler<TInputSchema, TOutput>,
})
}