@forjakit/auth-fastify 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/index.d.ts +11 -6
- package/dist/index.js +20 -17
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -52,6 +52,7 @@ app.register(authPlugin, {
|
|
|
52
52
|
| `onPasswordResetToken` | `(token, { email, tenantId }) => Promise<void>` | No | -- | Callback to send the reset email |
|
|
53
53
|
| `onVerificationToken` | `(token, { email, tenantId }) => Promise<void>` | No | -- | Callback to send the verification email |
|
|
54
54
|
| `cookie` | `AuthCookieOptions` | No | Disabled | Cookie-based token delivery configuration |
|
|
55
|
+
| `registration` | `boolean` | No | `true` | Set to `false` to skip `POST /register` (invite-only or seeded-account apps) |
|
|
55
56
|
|
|
56
57
|
## Routes
|
|
57
58
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { FastifyRequest, FastifyInstance, FastifyReply } from 'fastify';
|
|
2
2
|
import { AuthService, RateLimiter } from '@forjakit/auth';
|
|
3
|
+
import { z } from 'zod';
|
|
3
4
|
|
|
5
|
+
/** Any role enum a consumer may pass to `createAuthService`. */
|
|
6
|
+
type RoleEnum = z.ZodEnum<[string, ...string[]]>;
|
|
4
7
|
declare module "fastify" {
|
|
5
8
|
interface FastifyRequest {
|
|
6
9
|
authUser?: Record<string, unknown>;
|
|
@@ -24,9 +27,9 @@ interface AuthCookieOptions {
|
|
|
24
27
|
refreshTokenName?: string;
|
|
25
28
|
}
|
|
26
29
|
/** Options for registering the auth Fastify plugin. */
|
|
27
|
-
interface AuthPluginOptions {
|
|
30
|
+
interface AuthPluginOptions<T extends RoleEnum = RoleEnum> {
|
|
28
31
|
/** The auth service instance created via `createAuthService`. */
|
|
29
|
-
service: AuthService
|
|
32
|
+
service: AuthService<T>;
|
|
30
33
|
/** Custom function to extract the tenant ID from a request. Defaults to `x-tenant-id` header or subdomain. */
|
|
31
34
|
tenantResolver?: (req: FastifyRequest) => string;
|
|
32
35
|
/** Route prefix (e.g., "/auth"). */
|
|
@@ -47,6 +50,8 @@ interface AuthPluginOptions {
|
|
|
47
50
|
}) => Promise<void>;
|
|
48
51
|
/** Optional cookie configuration. When enabled, tokens are delivered via HttpOnly cookies instead of the response body. */
|
|
49
52
|
cookie?: AuthCookieOptions;
|
|
53
|
+
/** Whether to expose `POST /register`. Set to `false` for invite-only or seeded-account apps. Defaults to `true`. */
|
|
54
|
+
registration?: boolean;
|
|
50
55
|
}
|
|
51
56
|
/**
|
|
52
57
|
* Fastify plugin that registers auth routes and handles AuthError/ZodError responses.
|
|
@@ -55,21 +60,21 @@ interface AuthPluginOptions {
|
|
|
55
60
|
* @param fastify - Fastify instance.
|
|
56
61
|
* @param options - Plugin options including auth service, tenant resolver, and optional cookie configuration.
|
|
57
62
|
*/
|
|
58
|
-
declare function authPlugin(fastify: FastifyInstance, options: AuthPluginOptions): Promise<void>;
|
|
63
|
+
declare function authPlugin<T extends RoleEnum>(fastify: FastifyInstance, options: AuthPluginOptions<T>): Promise<void>;
|
|
59
64
|
/**
|
|
60
65
|
* Fastify preHandler that authenticates the request via Bearer token or cookie and populates `request.authUser`.
|
|
61
66
|
* @param service - The auth service instance.
|
|
62
67
|
* @param cookieAccessTokenName - Optional cookie name for the access token. When provided, checks cookies before the Authorization header.
|
|
63
68
|
* @returns Fastify preHandler function.
|
|
64
69
|
*/
|
|
65
|
-
declare function authenticate(service: AuthService
|
|
70
|
+
declare function authenticate<T extends RoleEnum>(service: AuthService<T>, cookieAccessTokenName?: string): (request: FastifyRequest, reply: FastifyReply) => Promise<undefined>;
|
|
66
71
|
/**
|
|
67
72
|
* Fastify preHandler that authenticates and checks if the user has one of the required roles.
|
|
68
73
|
* @param service - The auth service instance.
|
|
69
74
|
* @param roles - Allowed roles for this route. Optionally, the first argument can be a cookie access token name if it's not a valid role.
|
|
70
75
|
* @returns Fastify preHandler function.
|
|
71
76
|
*/
|
|
72
|
-
declare function requireRole(service: AuthService
|
|
77
|
+
declare function requireRole<T extends RoleEnum>(service: AuthService<T>, ...roles: string[]): (request: FastifyRequest, reply: FastifyReply) => Promise<undefined>;
|
|
73
78
|
/**
|
|
74
79
|
* Creates middleware factories that are pre-configured with cookie support.
|
|
75
80
|
* Use this when cookie mode is enabled to avoid passing the cookie name to every middleware call.
|
|
@@ -77,7 +82,7 @@ declare function requireRole(service: AuthService, ...roles: string[]): (request
|
|
|
77
82
|
* @param accessTokenName - Cookie name for the access token.
|
|
78
83
|
* @returns Object with `authenticate` and `requireRole` middleware factories.
|
|
79
84
|
*/
|
|
80
|
-
declare function createCookieMiddleware(service: AuthService
|
|
85
|
+
declare function createCookieMiddleware<T extends RoleEnum>(service: AuthService<T>, accessTokenName: string): {
|
|
81
86
|
/**
|
|
82
87
|
* Fastify preHandler that authenticates via cookie or Bearer token and populates `request.authUser`.
|
|
83
88
|
* @returns Fastify preHandler function.
|
package/dist/index.js
CHANGED
|
@@ -65,7 +65,8 @@ async function authPlugin(fastify, options) {
|
|
|
65
65
|
rateLimitKeyResolver = defaultRateLimitKeyResolver,
|
|
66
66
|
onPasswordResetToken,
|
|
67
67
|
onVerificationToken,
|
|
68
|
-
cookie
|
|
68
|
+
cookie,
|
|
69
|
+
registration = true
|
|
69
70
|
} = options;
|
|
70
71
|
const { schemas } = service;
|
|
71
72
|
const cookieEnabled = cookie?.enabled ?? false;
|
|
@@ -77,7 +78,7 @@ async function authPlugin(fastify, options) {
|
|
|
77
78
|
accessTokenName: cookie.accessTokenName ?? "access_token",
|
|
78
79
|
refreshTokenName: cookie.refreshTokenName ?? "refresh_token"
|
|
79
80
|
} : void 0;
|
|
80
|
-
if (cookieEnabled) {
|
|
81
|
+
if (cookieEnabled && !fastify.hasRequestDecorator("cookies")) {
|
|
81
82
|
await fastify.register(import("@fastify/cookie"));
|
|
82
83
|
}
|
|
83
84
|
function rateLimit(action) {
|
|
@@ -116,21 +117,23 @@ async function authPlugin(fastify, options) {
|
|
|
116
117
|
}
|
|
117
118
|
throw error;
|
|
118
119
|
});
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
120
|
+
if (registration) {
|
|
121
|
+
const registerOpts = rateLimit("register") ? { preHandler: rateLimit("register") } : {};
|
|
122
|
+
fastify.post("/register", registerOpts, async (request, reply) => {
|
|
123
|
+
const tenantId = tenantResolver(request);
|
|
124
|
+
const body = request.body;
|
|
125
|
+
const result = await service.register({ ...body, tenantId });
|
|
126
|
+
if (result.verificationToken && onVerificationToken) {
|
|
127
|
+
await onVerificationToken(result.verificationToken, { email: result.user.email, tenantId });
|
|
128
|
+
}
|
|
129
|
+
if (cookieEnabled && cookieOpts) {
|
|
130
|
+
setTokenCookies(reply, result, cookieOpts);
|
|
131
|
+
const { accessToken: _, refreshToken: __, ...rest } = result;
|
|
132
|
+
return reply.status(201).send(rest);
|
|
133
|
+
}
|
|
134
|
+
return reply.status(201).send(result);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
134
137
|
const loginOpts = rateLimit("login") ? { preHandler: rateLimit("login") } : {};
|
|
135
138
|
fastify.post("/login", loginOpts, async (request, reply) => {
|
|
136
139
|
const tenantId = tenantResolver(request);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { FastifyInstance, FastifyRequest, FastifyReply } from \"fastify\";\nimport { type AuthService, type RateLimiter, AuthError, Errors } from \"@forjakit/auth\";\nimport { ZodError } from \"zod\";\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n authUser?: Record<string, unknown>;\n }\n}\n\n/** Cookie configuration for secure token delivery. */\nexport interface AuthCookieOptions {\n /** Enable cookie-based token delivery. When true, tokens are set as HttpOnly cookies instead of returned in the response body. */\n enabled: boolean;\n /** Whether to set the Secure flag (HTTPS only). Defaults to `true` in production. */\n secure?: boolean;\n /** SameSite attribute for cookies. Defaults to `\"lax\"`. */\n sameSite?: \"strict\" | \"lax\" | \"none\";\n /** Cookie domain. Defaults to the request hostname. */\n domain?: string;\n /** Cookie path. Defaults to `\"/\"`. */\n path?: string;\n /** Name for the access token cookie. Defaults to `\"access_token\"`. */\n accessTokenName?: string;\n /** Name for the refresh token cookie. Defaults to `\"refresh_token\"`. */\n refreshTokenName?: string;\n}\n\n/** Options for registering the auth Fastify plugin. */\nexport interface AuthPluginOptions {\n /** The auth service instance created via `createAuthService`. */\n service: AuthService;\n /** Custom function to extract the tenant ID from a request. Defaults to `x-tenant-id` header or subdomain. */\n tenantResolver?: (req: FastifyRequest) => string;\n /** Route prefix (e.g., \"/auth\"). */\n prefix?: string;\n /** Optional rate limiter. Applied to login, register, and password reset routes. */\n rateLimiter?: RateLimiter;\n /** Custom function to extract the rate limit key from a request. Defaults to IP address. */\n rateLimitKeyResolver?: (req: FastifyRequest) => string;\n /** Optional callback invoked with the raw reset token and user info. Use this to send the reset email. */\n onPasswordResetToken?: (token: string, user: { email: string; tenantId: string }) => Promise<void>;\n /** Optional callback invoked with the raw verification token and user info. Use this to send the verification email. */\n onVerificationToken?: (token: string, user: { email: string; tenantId: string }) => Promise<void>;\n /** Optional cookie configuration. When enabled, tokens are delivered via HttpOnly cookies instead of the response body. */\n cookie?: AuthCookieOptions;\n}\n\nfunction defaultTenantResolver(req: FastifyRequest): string {\n const tenantId =\n (req.headers[\"x-tenant-id\"] as string) ??\n req.hostname.split(\".\")[0];\n\n if (!tenantId) {\n throw new AuthError(\"Tenant ID is required\", \"TENANT_REQUIRED\", 400);\n }\n\n return tenantId;\n}\n\nfunction defaultRateLimitKeyResolver(req: FastifyRequest): string {\n return req.ip;\n}\n\n/**\n * Sets access and refresh token cookies on the reply.\n * @param reply - Fastify reply object.\n * @param tokens - Object containing accessToken and refreshToken strings.\n * @param cookieOpts - Resolved cookie options.\n */\nfunction setTokenCookies(\n reply: FastifyReply,\n tokens: { accessToken: string; refreshToken: string },\n cookieOpts: Required<Pick<AuthCookieOptions, \"secure\" | \"sameSite\" | \"path\" | \"accessTokenName\" | \"refreshTokenName\">> & Pick<AuthCookieOptions, \"domain\">\n) {\n const baseOpts = {\n httpOnly: true,\n secure: cookieOpts.secure,\n sameSite: cookieOpts.sameSite,\n path: cookieOpts.path,\n ...(cookieOpts.domain ? { domain: cookieOpts.domain } : {}),\n };\n\n reply.setCookie(cookieOpts.accessTokenName, tokens.accessToken, {\n ...baseOpts,\n maxAge: 15 * 60, // 15 minutes — matches default access token expiry\n });\n\n reply.setCookie(cookieOpts.refreshTokenName, tokens.refreshToken, {\n ...baseOpts,\n maxAge: 7 * 24 * 60 * 60, // 7 days — matches default refresh token expiry\n });\n}\n\n/**\n * Clears token cookies from the reply.\n * @param reply - Fastify reply object.\n * @param cookieOpts - Resolved cookie options.\n */\nfunction clearTokenCookies(\n reply: FastifyReply,\n cookieOpts: Required<Pick<AuthCookieOptions, \"path\" | \"accessTokenName\" | \"refreshTokenName\">> & Pick<AuthCookieOptions, \"domain\">\n) {\n const clearOpts = {\n httpOnly: true,\n path: cookieOpts.path,\n ...(cookieOpts.domain ? { domain: cookieOpts.domain } : {}),\n maxAge: 0,\n };\n\n reply.setCookie(cookieOpts.accessTokenName, \"\", clearOpts);\n reply.setCookie(cookieOpts.refreshTokenName, \"\", clearOpts);\n}\n\n/**\n * Extracts the access token from the request. Checks cookies first (if cookie mode is enabled), then falls back to the Authorization header.\n * @param request - Fastify request object.\n * @param accessTokenName - Cookie name for the access token (undefined if cookie mode is disabled).\n * @returns The raw JWT access token string.\n * @throws AuthError if no token is found.\n */\nfunction extractToken(request: FastifyRequest, accessTokenName?: string): string {\n // Try cookie first\n if (accessTokenName) {\n const cookieToken = (request.cookies as Record<string, string | undefined>)?.[accessTokenName];\n if (cookieToken) return cookieToken;\n }\n\n // Fallback to Authorization header\n const header = request.headers.authorization;\n if (header?.startsWith(\"Bearer \")) {\n return header.slice(7);\n }\n\n throw new AuthError(\"Missing or invalid authorization header\", \"INVALID_TOKEN\", 401);\n}\n\n/**\n * Extracts the refresh token from the request. Checks cookies first (if cookie mode is enabled), then falls back to the request body.\n * @param request - Fastify request object.\n * @param refreshTokenName - Cookie name for the refresh token (undefined if cookie mode is disabled).\n * @returns The raw JWT refresh token string, or undefined if not found in cookies.\n */\nfunction extractRefreshToken(request: FastifyRequest, refreshTokenName?: string): string | undefined {\n if (refreshTokenName) {\n return (request.cookies as Record<string, string | undefined>)?.[refreshTokenName];\n }\n return undefined;\n}\n\n/**\n * Fastify plugin that registers auth routes and handles AuthError/ZodError responses.\n * Routes: POST /register, /login, /refresh, /logout, /request-password-reset, /reset-password, /verify-email, /resend-verification. GET /me.\n *\n * @param fastify - Fastify instance.\n * @param options - Plugin options including auth service, tenant resolver, and optional cookie configuration.\n */\nexport async function authPlugin(\n fastify: FastifyInstance,\n options: AuthPluginOptions\n) {\n const {\n service,\n tenantResolver = defaultTenantResolver,\n rateLimiter,\n rateLimitKeyResolver = defaultRateLimitKeyResolver,\n onPasswordResetToken,\n onVerificationToken,\n cookie,\n } = options;\n const { schemas } = service;\n\n // Resolve cookie options\n const cookieEnabled = cookie?.enabled ?? false;\n const cookieOpts = cookieEnabled\n ? {\n secure: cookie!.secure ?? process.env.NODE_ENV === \"production\",\n sameSite: cookie!.sameSite ?? (\"lax\" as const),\n domain: cookie!.domain,\n path: cookie!.path ?? \"/\",\n accessTokenName: cookie!.accessTokenName ?? \"access_token\",\n refreshTokenName: cookie!.refreshTokenName ?? \"refresh_token\",\n }\n : undefined;\n\n // Register @fastify/cookie if cookie mode is enabled\n if (cookieEnabled) {\n await fastify.register(import(\"@fastify/cookie\"));\n }\n\n // Rate limit preHandler factory\n function rateLimit(action: string) {\n if (!rateLimiter) return undefined;\n return async (request: FastifyRequest, reply: FastifyReply) => {\n const key = rateLimitKeyResolver(request);\n const result = await rateLimiter.consume(key, action);\n if (!result.allowed) {\n if (result.retryAfter) {\n reply.header(\"Retry-After\", String(result.retryAfter));\n }\n throw Errors.rateLimited(result.retryAfter);\n }\n };\n }\n\n // Error handler for AuthError and ZodError\n fastify.setErrorHandler((error, _request, reply) => {\n if (error instanceof AuthError) {\n const response: Record<string, unknown> = { error: error.code, message: error.message };\n if (error.statusCode === 429) {\n reply.header(\"Retry-After\", reply.getHeader(\"Retry-After\") ?? \"60\");\n }\n if (cookieEnabled && error.statusCode === 401) {\n clearTokenCookies(reply, cookieOpts!);\n }\n return reply.status(error.statusCode).send(response);\n }\n if (error instanceof ZodError) {\n return reply.status(400).send({\n error: \"VALIDATION_ERROR\",\n message: \"Invalid input\",\n details: error.errors.map((e) => ({\n field: e.path.join(\".\"),\n message: e.message,\n })),\n });\n }\n throw error;\n });\n\n // POST /register\n const registerOpts = rateLimit(\"register\") ? { preHandler: rateLimit(\"register\") } : {};\n fastify.post(\"/register\", registerOpts, async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n const result = await service.register({ ...body, tenantId } as Parameters<typeof service.register>[0]);\n\n if (result.verificationToken && onVerificationToken) {\n await onVerificationToken(result.verificationToken, { email: result.user.email, tenantId });\n }\n\n if (cookieEnabled && cookieOpts) {\n setTokenCookies(reply, result, cookieOpts);\n const { accessToken: _, refreshToken: __, ...rest } = result;\n return reply.status(201).send(rest);\n }\n\n return reply.status(201).send(result);\n });\n\n // POST /login\n const loginOpts = rateLimit(\"login\") ? { preHandler: rateLimit(\"login\") } : {};\n fastify.post(\"/login\", loginOpts, async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n const result = await service.login({ ...body, tenantId } as { email: string; password: string; tenantId: string });\n\n if (cookieEnabled && cookieOpts) {\n setTokenCookies(reply, result, cookieOpts);\n const { accessToken: _, refreshToken: __, ...rest } = result;\n return reply.status(200).send(rest);\n }\n\n return reply.status(200).send(result);\n });\n\n // POST /refresh\n fastify.post(\"/refresh\", async (request: FastifyRequest, reply: FastifyReply) => {\n let refreshToken: string;\n\n if (cookieEnabled && cookieOpts) {\n const cookieRefresh = extractRefreshToken(request, cookieOpts.refreshTokenName);\n if (cookieRefresh) {\n refreshToken = cookieRefresh;\n } else {\n const parsed = schemas.RefreshInput.parse(request.body);\n refreshToken = parsed.refreshToken;\n }\n } else {\n const parsed = schemas.RefreshInput.parse(request.body);\n refreshToken = parsed.refreshToken;\n }\n\n const result = await service.refresh(refreshToken);\n\n if (cookieEnabled && cookieOpts) {\n setTokenCookies(reply, result, cookieOpts);\n return reply.status(200).send({ message: \"Tokens refreshed\" });\n }\n\n return reply.status(200).send(result);\n });\n\n // GET /me\n fastify.get(\"/me\", async (request: FastifyRequest, reply: FastifyReply) => {\n const token = extractToken(request, cookieOpts?.accessTokenName);\n const user = await service.authenticate(token);\n\n return reply.status(200).send(user);\n });\n\n // POST /logout\n fastify.post(\"/logout\", async (request: FastifyRequest, reply: FastifyReply) => {\n const token = extractToken(request, cookieOpts?.accessTokenName);\n await service.logout(token);\n\n if (cookieEnabled && cookieOpts) {\n clearTokenCookies(reply, cookieOpts);\n }\n\n return reply.status(204).send();\n });\n\n // POST /request-password-reset\n const resetReqOpts = rateLimit(\"request-password-reset\") ? { preHandler: rateLimit(\"request-password-reset\") } : {};\n fastify.post(\"/request-password-reset\", resetReqOpts, async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n const parsed = schemas.RequestPasswordResetInput.parse({ ...body, tenantId });\n\n const token = await service.requestPasswordReset(parsed.email, parsed.tenantId);\n\n if (token && onPasswordResetToken) {\n await onPasswordResetToken(token, { email: parsed.email, tenantId });\n }\n\n // Always return success to avoid leaking whether the email exists\n return reply.status(200).send({ message: \"If the email exists, a reset link has been sent\" });\n });\n\n // POST /reset-password\n fastify.post(\"/reset-password\", async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n await service.resetPassword(\n (body as { token: string }).token,\n (body as { password: string }).password,\n tenantId\n );\n\n return reply.status(200).send({ message: \"Password has been reset\" });\n });\n\n // POST /verify-email\n fastify.post(\"/verify-email\", async (request: FastifyRequest, reply: FastifyReply) => {\n const body = request.body as Record<string, unknown>;\n const parsed = schemas.VerifyEmailInput.parse(body);\n await service.verifyEmail(parsed.token);\n\n return reply.status(200).send({ message: \"Email verified\" });\n });\n\n // POST /resend-verification\n fastify.post(\"/resend-verification\", async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n const email = (body as { email: string }).email;\n\n const token = await service.resendVerificationEmail(email, tenantId);\n\n if (token && onVerificationToken) {\n await onVerificationToken(token, { email, tenantId });\n }\n\n return reply.status(200).send({ message: \"If the email exists, a verification link has been sent\" });\n });\n}\n\n// -- Middleware factories --\n\n/**\n * Fastify preHandler that authenticates the request via Bearer token or cookie and populates `request.authUser`.\n * @param service - The auth service instance.\n * @param cookieAccessTokenName - Optional cookie name for the access token. When provided, checks cookies before the Authorization header.\n * @returns Fastify preHandler function.\n */\nexport function authenticate(service: AuthService, cookieAccessTokenName?: string) {\n return async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const token = extractToken(request, cookieAccessTokenName);\n request.authUser = await service.authenticate(token);\n } catch (error) {\n if (error instanceof AuthError) {\n return reply\n .status(error.statusCode)\n .send({ error: error.code, message: error.message });\n }\n throw error;\n }\n };\n}\n\n/**\n * Fastify preHandler that authenticates and checks if the user has one of the required roles.\n * @param service - The auth service instance.\n * @param roles - Allowed roles for this route. Optionally, the first argument can be a cookie access token name if it's not a valid role.\n * @returns Fastify preHandler function.\n */\nexport function requireRole(service: AuthService, ...roles: string[]) {\n const checkRole = service.authorize(...roles);\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const token = extractToken(request);\n const user = await service.authenticate(token);\n checkRole(user);\n request.authUser = user;\n } catch (error) {\n if (error instanceof AuthError) {\n return reply\n .status(error.statusCode)\n .send({ error: error.code, message: error.message });\n }\n throw error;\n }\n };\n}\n\n/**\n * Creates middleware factories that are pre-configured with cookie support.\n * Use this when cookie mode is enabled to avoid passing the cookie name to every middleware call.\n * @param service - The auth service instance.\n * @param accessTokenName - Cookie name for the access token.\n * @returns Object with `authenticate` and `requireRole` middleware factories.\n */\nexport function createCookieMiddleware(service: AuthService, accessTokenName: string) {\n return {\n /**\n * Fastify preHandler that authenticates via cookie or Bearer token and populates `request.authUser`.\n * @returns Fastify preHandler function.\n */\n authenticate() {\n return async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const token = extractToken(request, accessTokenName);\n request.authUser = await service.authenticate(token);\n } catch (error) {\n if (error instanceof AuthError) {\n return reply\n .status(error.statusCode)\n .send({ error: error.code, message: error.message });\n }\n throw error;\n }\n };\n },\n /**\n * Fastify preHandler that authenticates via cookie or Bearer token and checks if the user has one of the required roles.\n * @param roles - Allowed roles for this route.\n * @returns Fastify preHandler function.\n */\n requireRole(...roles: string[]) {\n const checkRole = service.authorize(...roles);\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const token = extractToken(request, accessTokenName);\n const user = await service.authenticate(token);\n checkRole(user);\n request.authUser = user;\n } catch (error) {\n if (error instanceof AuthError) {\n return reply\n .status(error.statusCode)\n .send({ error: error.code, message: error.message });\n }\n throw error;\n }\n };\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/plugin.ts"],"sourcesContent":["import type { FastifyInstance, FastifyRequest, FastifyReply } from \"fastify\";\nimport { type AuthService, type RateLimiter, AuthError, Errors } from \"@forjakit/auth\";\nimport { ZodError, type z } from \"zod\";\n\n/** Any role enum a consumer may pass to `createAuthService`. */\ntype RoleEnum = z.ZodEnum<[string, ...string[]]>;\n\ndeclare module \"fastify\" {\n interface FastifyRequest {\n authUser?: Record<string, unknown>;\n }\n}\n\n/** Cookie configuration for secure token delivery. */\nexport interface AuthCookieOptions {\n /** Enable cookie-based token delivery. When true, tokens are set as HttpOnly cookies instead of returned in the response body. */\n enabled: boolean;\n /** Whether to set the Secure flag (HTTPS only). Defaults to `true` in production. */\n secure?: boolean;\n /** SameSite attribute for cookies. Defaults to `\"lax\"`. */\n sameSite?: \"strict\" | \"lax\" | \"none\";\n /** Cookie domain. Defaults to the request hostname. */\n domain?: string;\n /** Cookie path. Defaults to `\"/\"`. */\n path?: string;\n /** Name for the access token cookie. Defaults to `\"access_token\"`. */\n accessTokenName?: string;\n /** Name for the refresh token cookie. Defaults to `\"refresh_token\"`. */\n refreshTokenName?: string;\n}\n\n/** Options for registering the auth Fastify plugin. */\nexport interface AuthPluginOptions<T extends RoleEnum = RoleEnum> {\n /** The auth service instance created via `createAuthService`. */\n service: AuthService<T>;\n /** Custom function to extract the tenant ID from a request. Defaults to `x-tenant-id` header or subdomain. */\n tenantResolver?: (req: FastifyRequest) => string;\n /** Route prefix (e.g., \"/auth\"). */\n prefix?: string;\n /** Optional rate limiter. Applied to login, register, and password reset routes. */\n rateLimiter?: RateLimiter;\n /** Custom function to extract the rate limit key from a request. Defaults to IP address. */\n rateLimitKeyResolver?: (req: FastifyRequest) => string;\n /** Optional callback invoked with the raw reset token and user info. Use this to send the reset email. */\n onPasswordResetToken?: (token: string, user: { email: string; tenantId: string }) => Promise<void>;\n /** Optional callback invoked with the raw verification token and user info. Use this to send the verification email. */\n onVerificationToken?: (token: string, user: { email: string; tenantId: string }) => Promise<void>;\n /** Optional cookie configuration. When enabled, tokens are delivered via HttpOnly cookies instead of the response body. */\n cookie?: AuthCookieOptions;\n /** Whether to expose `POST /register`. Set to `false` for invite-only or seeded-account apps. Defaults to `true`. */\n registration?: boolean;\n}\n\nfunction defaultTenantResolver(req: FastifyRequest): string {\n const tenantId =\n (req.headers[\"x-tenant-id\"] as string) ??\n req.hostname.split(\".\")[0];\n\n if (!tenantId) {\n throw new AuthError(\"Tenant ID is required\", \"TENANT_REQUIRED\", 400);\n }\n\n return tenantId;\n}\n\nfunction defaultRateLimitKeyResolver(req: FastifyRequest): string {\n return req.ip;\n}\n\n/**\n * Sets access and refresh token cookies on the reply.\n * @param reply - Fastify reply object.\n * @param tokens - Object containing accessToken and refreshToken strings.\n * @param cookieOpts - Resolved cookie options.\n */\nfunction setTokenCookies(\n reply: FastifyReply,\n tokens: { accessToken: string; refreshToken: string },\n cookieOpts: Required<Pick<AuthCookieOptions, \"secure\" | \"sameSite\" | \"path\" | \"accessTokenName\" | \"refreshTokenName\">> & Pick<AuthCookieOptions, \"domain\">\n) {\n const baseOpts = {\n httpOnly: true,\n secure: cookieOpts.secure,\n sameSite: cookieOpts.sameSite,\n path: cookieOpts.path,\n ...(cookieOpts.domain ? { domain: cookieOpts.domain } : {}),\n };\n\n reply.setCookie(cookieOpts.accessTokenName, tokens.accessToken, {\n ...baseOpts,\n maxAge: 15 * 60, // 15 minutes — matches default access token expiry\n });\n\n reply.setCookie(cookieOpts.refreshTokenName, tokens.refreshToken, {\n ...baseOpts,\n maxAge: 7 * 24 * 60 * 60, // 7 days — matches default refresh token expiry\n });\n}\n\n/**\n * Clears token cookies from the reply.\n * @param reply - Fastify reply object.\n * @param cookieOpts - Resolved cookie options.\n */\nfunction clearTokenCookies(\n reply: FastifyReply,\n cookieOpts: Required<Pick<AuthCookieOptions, \"path\" | \"accessTokenName\" | \"refreshTokenName\">> & Pick<AuthCookieOptions, \"domain\">\n) {\n const clearOpts = {\n httpOnly: true,\n path: cookieOpts.path,\n ...(cookieOpts.domain ? { domain: cookieOpts.domain } : {}),\n maxAge: 0,\n };\n\n reply.setCookie(cookieOpts.accessTokenName, \"\", clearOpts);\n reply.setCookie(cookieOpts.refreshTokenName, \"\", clearOpts);\n}\n\n/**\n * Extracts the access token from the request. Checks cookies first (if cookie mode is enabled), then falls back to the Authorization header.\n * @param request - Fastify request object.\n * @param accessTokenName - Cookie name for the access token (undefined if cookie mode is disabled).\n * @returns The raw JWT access token string.\n * @throws AuthError if no token is found.\n */\nfunction extractToken(request: FastifyRequest, accessTokenName?: string): string {\n // Try cookie first\n if (accessTokenName) {\n const cookieToken = (request.cookies as Record<string, string | undefined>)?.[accessTokenName];\n if (cookieToken) return cookieToken;\n }\n\n // Fallback to Authorization header\n const header = request.headers.authorization;\n if (header?.startsWith(\"Bearer \")) {\n return header.slice(7);\n }\n\n throw new AuthError(\"Missing or invalid authorization header\", \"INVALID_TOKEN\", 401);\n}\n\n/**\n * Extracts the refresh token from the request. Checks cookies first (if cookie mode is enabled), then falls back to the request body.\n * @param request - Fastify request object.\n * @param refreshTokenName - Cookie name for the refresh token (undefined if cookie mode is disabled).\n * @returns The raw JWT refresh token string, or undefined if not found in cookies.\n */\nfunction extractRefreshToken(request: FastifyRequest, refreshTokenName?: string): string | undefined {\n if (refreshTokenName) {\n return (request.cookies as Record<string, string | undefined>)?.[refreshTokenName];\n }\n return undefined;\n}\n\n/**\n * Fastify plugin that registers auth routes and handles AuthError/ZodError responses.\n * Routes: POST /register, /login, /refresh, /logout, /request-password-reset, /reset-password, /verify-email, /resend-verification. GET /me.\n *\n * @param fastify - Fastify instance.\n * @param options - Plugin options including auth service, tenant resolver, and optional cookie configuration.\n */\nexport async function authPlugin<T extends RoleEnum>(\n fastify: FastifyInstance,\n options: AuthPluginOptions<T>\n) {\n const {\n service,\n tenantResolver = defaultTenantResolver,\n rateLimiter,\n rateLimitKeyResolver = defaultRateLimitKeyResolver,\n onPasswordResetToken,\n onVerificationToken,\n cookie,\n registration = true,\n } = options;\n const { schemas } = service;\n\n // Resolve cookie options\n const cookieEnabled = cookie?.enabled ?? false;\n const cookieOpts = cookieEnabled\n ? {\n secure: cookie!.secure ?? process.env.NODE_ENV === \"production\",\n sameSite: cookie!.sameSite ?? (\"lax\" as const),\n domain: cookie!.domain,\n path: cookie!.path ?? \"/\",\n accessTokenName: cookie!.accessTokenName ?? \"access_token\",\n refreshTokenName: cookie!.refreshTokenName ?? \"refresh_token\",\n }\n : undefined;\n\n // Register @fastify/cookie if cookie mode is enabled, unless the app already did\n // (apps register it globally so their own protected routes can read the session cookie).\n if (cookieEnabled && !fastify.hasRequestDecorator(\"cookies\")) {\n await fastify.register(import(\"@fastify/cookie\"));\n }\n\n // Rate limit preHandler factory\n function rateLimit(action: string) {\n if (!rateLimiter) return undefined;\n return async (request: FastifyRequest, reply: FastifyReply) => {\n const key = rateLimitKeyResolver(request);\n const result = await rateLimiter.consume(key, action);\n if (!result.allowed) {\n if (result.retryAfter) {\n reply.header(\"Retry-After\", String(result.retryAfter));\n }\n throw Errors.rateLimited(result.retryAfter);\n }\n };\n }\n\n // Error handler for AuthError and ZodError\n fastify.setErrorHandler((error, _request, reply) => {\n if (error instanceof AuthError) {\n const response: Record<string, unknown> = { error: error.code, message: error.message };\n if (error.statusCode === 429) {\n reply.header(\"Retry-After\", reply.getHeader(\"Retry-After\") ?? \"60\");\n }\n if (cookieEnabled && error.statusCode === 401) {\n clearTokenCookies(reply, cookieOpts!);\n }\n return reply.status(error.statusCode).send(response);\n }\n if (error instanceof ZodError) {\n return reply.status(400).send({\n error: \"VALIDATION_ERROR\",\n message: \"Invalid input\",\n details: error.errors.map((e) => ({\n field: e.path.join(\".\"),\n message: e.message,\n })),\n });\n }\n throw error;\n });\n\n // POST /register\n if (registration) {\n const registerOpts = rateLimit(\"register\") ? { preHandler: rateLimit(\"register\") } : {};\n fastify.post(\"/register\", registerOpts, async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n const result = await service.register({ ...body, tenantId } as Parameters<typeof service.register>[0]);\n\n if (result.verificationToken && onVerificationToken) {\n await onVerificationToken(result.verificationToken, { email: result.user.email, tenantId });\n }\n\n if (cookieEnabled && cookieOpts) {\n setTokenCookies(reply, result, cookieOpts);\n const { accessToken: _, refreshToken: __, ...rest } = result;\n return reply.status(201).send(rest);\n }\n\n return reply.status(201).send(result);\n });\n }\n\n // POST /login\n const loginOpts = rateLimit(\"login\") ? { preHandler: rateLimit(\"login\") } : {};\n fastify.post(\"/login\", loginOpts, async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n const result = await service.login({ ...body, tenantId } as { email: string; password: string; tenantId: string });\n\n if (cookieEnabled && cookieOpts) {\n setTokenCookies(reply, result, cookieOpts);\n const { accessToken: _, refreshToken: __, ...rest } = result;\n return reply.status(200).send(rest);\n }\n\n return reply.status(200).send(result);\n });\n\n // POST /refresh\n fastify.post(\"/refresh\", async (request: FastifyRequest, reply: FastifyReply) => {\n let refreshToken: string;\n\n if (cookieEnabled && cookieOpts) {\n const cookieRefresh = extractRefreshToken(request, cookieOpts.refreshTokenName);\n if (cookieRefresh) {\n refreshToken = cookieRefresh;\n } else {\n const parsed = schemas.RefreshInput.parse(request.body);\n refreshToken = parsed.refreshToken;\n }\n } else {\n const parsed = schemas.RefreshInput.parse(request.body);\n refreshToken = parsed.refreshToken;\n }\n\n const result = await service.refresh(refreshToken);\n\n if (cookieEnabled && cookieOpts) {\n setTokenCookies(reply, result, cookieOpts);\n return reply.status(200).send({ message: \"Tokens refreshed\" });\n }\n\n return reply.status(200).send(result);\n });\n\n // GET /me\n fastify.get(\"/me\", async (request: FastifyRequest, reply: FastifyReply) => {\n const token = extractToken(request, cookieOpts?.accessTokenName);\n const user = await service.authenticate(token);\n\n return reply.status(200).send(user);\n });\n\n // POST /logout\n fastify.post(\"/logout\", async (request: FastifyRequest, reply: FastifyReply) => {\n const token = extractToken(request, cookieOpts?.accessTokenName);\n await service.logout(token);\n\n if (cookieEnabled && cookieOpts) {\n clearTokenCookies(reply, cookieOpts);\n }\n\n return reply.status(204).send();\n });\n\n // POST /request-password-reset\n const resetReqOpts = rateLimit(\"request-password-reset\") ? { preHandler: rateLimit(\"request-password-reset\") } : {};\n fastify.post(\"/request-password-reset\", resetReqOpts, async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n const parsed = schemas.RequestPasswordResetInput.parse({ ...body, tenantId });\n\n const token = await service.requestPasswordReset(parsed.email, parsed.tenantId);\n\n if (token && onPasswordResetToken) {\n await onPasswordResetToken(token, { email: parsed.email, tenantId });\n }\n\n // Always return success to avoid leaking whether the email exists\n return reply.status(200).send({ message: \"If the email exists, a reset link has been sent\" });\n });\n\n // POST /reset-password\n fastify.post(\"/reset-password\", async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n await service.resetPassword(\n (body as { token: string }).token,\n (body as { password: string }).password,\n tenantId\n );\n\n return reply.status(200).send({ message: \"Password has been reset\" });\n });\n\n // POST /verify-email\n fastify.post(\"/verify-email\", async (request: FastifyRequest, reply: FastifyReply) => {\n const body = request.body as Record<string, unknown>;\n const parsed = schemas.VerifyEmailInput.parse(body);\n await service.verifyEmail(parsed.token);\n\n return reply.status(200).send({ message: \"Email verified\" });\n });\n\n // POST /resend-verification\n fastify.post(\"/resend-verification\", async (request: FastifyRequest, reply: FastifyReply) => {\n const tenantId = tenantResolver(request);\n const body = request.body as Record<string, unknown>;\n const email = (body as { email: string }).email;\n\n const token = await service.resendVerificationEmail(email, tenantId);\n\n if (token && onVerificationToken) {\n await onVerificationToken(token, { email, tenantId });\n }\n\n return reply.status(200).send({ message: \"If the email exists, a verification link has been sent\" });\n });\n}\n\n// -- Middleware factories --\n\n/**\n * Fastify preHandler that authenticates the request via Bearer token or cookie and populates `request.authUser`.\n * @param service - The auth service instance.\n * @param cookieAccessTokenName - Optional cookie name for the access token. When provided, checks cookies before the Authorization header.\n * @returns Fastify preHandler function.\n */\nexport function authenticate<T extends RoleEnum>(service: AuthService<T>, cookieAccessTokenName?: string) {\n return async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const token = extractToken(request, cookieAccessTokenName);\n request.authUser = await service.authenticate(token);\n } catch (error) {\n if (error instanceof AuthError) {\n return reply\n .status(error.statusCode)\n .send({ error: error.code, message: error.message });\n }\n throw error;\n }\n };\n}\n\n/**\n * Fastify preHandler that authenticates and checks if the user has one of the required roles.\n * @param service - The auth service instance.\n * @param roles - Allowed roles for this route. Optionally, the first argument can be a cookie access token name if it's not a valid role.\n * @returns Fastify preHandler function.\n */\nexport function requireRole<T extends RoleEnum>(service: AuthService<T>, ...roles: string[]) {\n const checkRole = service.authorize(...roles);\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const token = extractToken(request);\n const user = await service.authenticate(token);\n checkRole(user);\n request.authUser = user;\n } catch (error) {\n if (error instanceof AuthError) {\n return reply\n .status(error.statusCode)\n .send({ error: error.code, message: error.message });\n }\n throw error;\n }\n };\n}\n\n/**\n * Creates middleware factories that are pre-configured with cookie support.\n * Use this when cookie mode is enabled to avoid passing the cookie name to every middleware call.\n * @param service - The auth service instance.\n * @param accessTokenName - Cookie name for the access token.\n * @returns Object with `authenticate` and `requireRole` middleware factories.\n */\nexport function createCookieMiddleware<T extends RoleEnum>(service: AuthService<T>, accessTokenName: string) {\n return {\n /**\n * Fastify preHandler that authenticates via cookie or Bearer token and populates `request.authUser`.\n * @returns Fastify preHandler function.\n */\n authenticate() {\n return async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const token = extractToken(request, accessTokenName);\n request.authUser = await service.authenticate(token);\n } catch (error) {\n if (error instanceof AuthError) {\n return reply\n .status(error.statusCode)\n .send({ error: error.code, message: error.message });\n }\n throw error;\n }\n };\n },\n /**\n * Fastify preHandler that authenticates via cookie or Bearer token and checks if the user has one of the required roles.\n * @param roles - Allowed roles for this route.\n * @returns Fastify preHandler function.\n */\n requireRole(...roles: string[]) {\n const checkRole = service.authorize(...roles);\n\n return async (request: FastifyRequest, reply: FastifyReply) => {\n try {\n const token = extractToken(request, accessTokenName);\n const user = await service.authenticate(token);\n checkRole(user);\n request.authUser = user;\n } catch (error) {\n if (error instanceof AuthError) {\n return reply\n .status(error.statusCode)\n .send({ error: error.code, message: error.message });\n }\n throw error;\n }\n };\n },\n };\n}\n"],"mappings":";AACA,SAA6C,WAAW,cAAc;AACtE,SAAS,gBAAwB;AAmDjC,SAAS,sBAAsB,KAA6B;AAC1D,QAAM,WACH,IAAI,QAAQ,aAAa,KAC1B,IAAI,SAAS,MAAM,GAAG,EAAE,CAAC;AAE3B,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,UAAU,yBAAyB,mBAAmB,GAAG;AAAA,EACrE;AAEA,SAAO;AACT;AAEA,SAAS,4BAA4B,KAA6B;AAChE,SAAO,IAAI;AACb;AAQA,SAAS,gBACP,OACA,QACA,YACA;AACA,QAAM,WAAW;AAAA,IACf,UAAU;AAAA,IACV,QAAQ,WAAW;AAAA,IACnB,UAAU,WAAW;AAAA,IACrB,MAAM,WAAW;AAAA,IACjB,GAAI,WAAW,SAAS,EAAE,QAAQ,WAAW,OAAO,IAAI,CAAC;AAAA,EAC3D;AAEA,QAAM,UAAU,WAAW,iBAAiB,OAAO,aAAa;AAAA,IAC9D,GAAG;AAAA,IACH,QAAQ,KAAK;AAAA;AAAA,EACf,CAAC;AAED,QAAM,UAAU,WAAW,kBAAkB,OAAO,cAAc;AAAA,IAChE,GAAG;AAAA,IACH,QAAQ,IAAI,KAAK,KAAK;AAAA;AAAA,EACxB,CAAC;AACH;AAOA,SAAS,kBACP,OACA,YACA;AACA,QAAM,YAAY;AAAA,IAChB,UAAU;AAAA,IACV,MAAM,WAAW;AAAA,IACjB,GAAI,WAAW,SAAS,EAAE,QAAQ,WAAW,OAAO,IAAI,CAAC;AAAA,IACzD,QAAQ;AAAA,EACV;AAEA,QAAM,UAAU,WAAW,iBAAiB,IAAI,SAAS;AACzD,QAAM,UAAU,WAAW,kBAAkB,IAAI,SAAS;AAC5D;AASA,SAAS,aAAa,SAAyB,iBAAkC;AAE/E,MAAI,iBAAiB;AACnB,UAAM,cAAe,QAAQ,UAAiD,eAAe;AAC7F,QAAI,YAAa,QAAO;AAAA,EAC1B;AAGA,QAAM,SAAS,QAAQ,QAAQ;AAC/B,MAAI,QAAQ,WAAW,SAAS,GAAG;AACjC,WAAO,OAAO,MAAM,CAAC;AAAA,EACvB;AAEA,QAAM,IAAI,UAAU,2CAA2C,iBAAiB,GAAG;AACrF;AAQA,SAAS,oBAAoB,SAAyB,kBAA+C;AACnG,MAAI,kBAAkB;AACpB,WAAQ,QAAQ,UAAiD,gBAAgB;AAAA,EACnF;AACA,SAAO;AACT;AASA,eAAsB,WACpB,SACA,SACA;AACA,QAAM;AAAA,IACJ;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA,uBAAuB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA,IACA,eAAe;AAAA,EACjB,IAAI;AACJ,QAAM,EAAE,QAAQ,IAAI;AAGpB,QAAM,gBAAgB,QAAQ,WAAW;AACzC,QAAM,aAAa,gBACf;AAAA,IACE,QAAQ,OAAQ,UAAU,QAAQ,IAAI,aAAa;AAAA,IACnD,UAAU,OAAQ,YAAa;AAAA,IAC/B,QAAQ,OAAQ;AAAA,IAChB,MAAM,OAAQ,QAAQ;AAAA,IACtB,iBAAiB,OAAQ,mBAAmB;AAAA,IAC5C,kBAAkB,OAAQ,oBAAoB;AAAA,EAChD,IACA;AAIJ,MAAI,iBAAiB,CAAC,QAAQ,oBAAoB,SAAS,GAAG;AAC5D,UAAM,QAAQ,SAAS,OAAO,iBAAiB,CAAC;AAAA,EAClD;AAGA,WAAS,UAAU,QAAgB;AACjC,QAAI,CAAC,YAAa,QAAO;AACzB,WAAO,OAAO,SAAyB,UAAwB;AAC7D,YAAM,MAAM,qBAAqB,OAAO;AACxC,YAAM,SAAS,MAAM,YAAY,QAAQ,KAAK,MAAM;AACpD,UAAI,CAAC,OAAO,SAAS;AACnB,YAAI,OAAO,YAAY;AACrB,gBAAM,OAAO,eAAe,OAAO,OAAO,UAAU,CAAC;AAAA,QACvD;AACA,cAAM,OAAO,YAAY,OAAO,UAAU;AAAA,MAC5C;AAAA,IACF;AAAA,EACF;AAGA,UAAQ,gBAAgB,CAAC,OAAO,UAAU,UAAU;AAClD,QAAI,iBAAiB,WAAW;AAC9B,YAAM,WAAoC,EAAE,OAAO,MAAM,MAAM,SAAS,MAAM,QAAQ;AACtF,UAAI,MAAM,eAAe,KAAK;AAC5B,cAAM,OAAO,eAAe,MAAM,UAAU,aAAa,KAAK,IAAI;AAAA,MACpE;AACA,UAAI,iBAAiB,MAAM,eAAe,KAAK;AAC7C,0BAAkB,OAAO,UAAW;AAAA,MACtC;AACA,aAAO,MAAM,OAAO,MAAM,UAAU,EAAE,KAAK,QAAQ;AAAA,IACrD;AACA,QAAI,iBAAiB,UAAU;AAC7B,aAAO,MAAM,OAAO,GAAG,EAAE,KAAK;AAAA,QAC5B,OAAO;AAAA,QACP,SAAS;AAAA,QACT,SAAS,MAAM,OAAO,IAAI,CAAC,OAAO;AAAA,UAChC,OAAO,EAAE,KAAK,KAAK,GAAG;AAAA,UACtB,SAAS,EAAE;AAAA,QACb,EAAE;AAAA,MACJ,CAAC;AAAA,IACH;AACA,UAAM;AAAA,EACR,CAAC;AAGD,MAAI,cAAc;AAChB,UAAM,eAAe,UAAU,UAAU,IAAI,EAAE,YAAY,UAAU,UAAU,EAAE,IAAI,CAAC;AACtF,YAAQ,KAAK,aAAa,cAAc,OAAO,SAAyB,UAAwB;AAC9F,YAAM,WAAW,eAAe,OAAO;AACvC,YAAM,OAAO,QAAQ;AACrB,YAAM,SAAS,MAAM,QAAQ,SAAS,EAAE,GAAG,MAAM,SAAS,CAA2C;AAErG,UAAI,OAAO,qBAAqB,qBAAqB;AACnD,cAAM,oBAAoB,OAAO,mBAAmB,EAAE,OAAO,OAAO,KAAK,OAAO,SAAS,CAAC;AAAA,MAC5F;AAEA,UAAI,iBAAiB,YAAY;AAC/B,wBAAgB,OAAO,QAAQ,UAAU;AACzC,cAAM,EAAE,aAAa,GAAG,cAAc,IAAI,GAAG,KAAK,IAAI;AACtD,eAAO,MAAM,OAAO,GAAG,EAAE,KAAK,IAAI;AAAA,MACpC;AAEA,aAAO,MAAM,OAAO,GAAG,EAAE,KAAK,MAAM;AAAA,IACtC,CAAC;AAAA,EACH;AAGA,QAAM,YAAY,UAAU,OAAO,IAAI,EAAE,YAAY,UAAU,OAAO,EAAE,IAAI,CAAC;AAC7E,UAAQ,KAAK,UAAU,WAAW,OAAO,SAAyB,UAAwB;AACxF,UAAM,WAAW,eAAe,OAAO;AACvC,UAAM,OAAO,QAAQ;AACrB,UAAM,SAAS,MAAM,QAAQ,MAAM,EAAE,GAAG,MAAM,SAAS,CAA0D;AAEjH,QAAI,iBAAiB,YAAY;AAC/B,sBAAgB,OAAO,QAAQ,UAAU;AACzC,YAAM,EAAE,aAAa,GAAG,cAAc,IAAI,GAAG,KAAK,IAAI;AACtD,aAAO,MAAM,OAAO,GAAG,EAAE,KAAK,IAAI;AAAA,IACpC;AAEA,WAAO,MAAM,OAAO,GAAG,EAAE,KAAK,MAAM;AAAA,EACtC,CAAC;AAGD,UAAQ,KAAK,YAAY,OAAO,SAAyB,UAAwB;AAC/E,QAAI;AAEJ,QAAI,iBAAiB,YAAY;AAC/B,YAAM,gBAAgB,oBAAoB,SAAS,WAAW,gBAAgB;AAC9E,UAAI,eAAe;AACjB,uBAAe;AAAA,MACjB,OAAO;AACL,cAAM,SAAS,QAAQ,aAAa,MAAM,QAAQ,IAAI;AACtD,uBAAe,OAAO;AAAA,MACxB;AAAA,IACF,OAAO;AACL,YAAM,SAAS,QAAQ,aAAa,MAAM,QAAQ,IAAI;AACtD,qBAAe,OAAO;AAAA,IACxB;AAEA,UAAM,SAAS,MAAM,QAAQ,QAAQ,YAAY;AAEjD,QAAI,iBAAiB,YAAY;AAC/B,sBAAgB,OAAO,QAAQ,UAAU;AACzC,aAAO,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,mBAAmB,CAAC;AAAA,IAC/D;AAEA,WAAO,MAAM,OAAO,GAAG,EAAE,KAAK,MAAM;AAAA,EACtC,CAAC;AAGD,UAAQ,IAAI,OAAO,OAAO,SAAyB,UAAwB;AACzE,UAAM,QAAQ,aAAa,SAAS,YAAY,eAAe;AAC/D,UAAM,OAAO,MAAM,QAAQ,aAAa,KAAK;AAE7C,WAAO,MAAM,OAAO,GAAG,EAAE,KAAK,IAAI;AAAA,EACpC,CAAC;AAGD,UAAQ,KAAK,WAAW,OAAO,SAAyB,UAAwB;AAC9E,UAAM,QAAQ,aAAa,SAAS,YAAY,eAAe;AAC/D,UAAM,QAAQ,OAAO,KAAK;AAE1B,QAAI,iBAAiB,YAAY;AAC/B,wBAAkB,OAAO,UAAU;AAAA,IACrC;AAEA,WAAO,MAAM,OAAO,GAAG,EAAE,KAAK;AAAA,EAChC,CAAC;AAGD,QAAM,eAAe,UAAU,wBAAwB,IAAI,EAAE,YAAY,UAAU,wBAAwB,EAAE,IAAI,CAAC;AAClH,UAAQ,KAAK,2BAA2B,cAAc,OAAO,SAAyB,UAAwB;AAC5G,UAAM,WAAW,eAAe,OAAO;AACvC,UAAM,OAAO,QAAQ;AACrB,UAAM,SAAS,QAAQ,0BAA0B,MAAM,EAAE,GAAG,MAAM,SAAS,CAAC;AAE5E,UAAM,QAAQ,MAAM,QAAQ,qBAAqB,OAAO,OAAO,OAAO,QAAQ;AAE9E,QAAI,SAAS,sBAAsB;AACjC,YAAM,qBAAqB,OAAO,EAAE,OAAO,OAAO,OAAO,SAAS,CAAC;AAAA,IACrE;AAGA,WAAO,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,kDAAkD,CAAC;AAAA,EAC9F,CAAC;AAGD,UAAQ,KAAK,mBAAmB,OAAO,SAAyB,UAAwB;AACtF,UAAM,WAAW,eAAe,OAAO;AACvC,UAAM,OAAO,QAAQ;AACrB,UAAM,QAAQ;AAAA,MACX,KAA2B;AAAA,MAC3B,KAA8B;AAAA,MAC/B;AAAA,IACF;AAEA,WAAO,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,0BAA0B,CAAC;AAAA,EACtE,CAAC;AAGD,UAAQ,KAAK,iBAAiB,OAAO,SAAyB,UAAwB;AACpF,UAAM,OAAO,QAAQ;AACrB,UAAM,SAAS,QAAQ,iBAAiB,MAAM,IAAI;AAClD,UAAM,QAAQ,YAAY,OAAO,KAAK;AAEtC,WAAO,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,iBAAiB,CAAC;AAAA,EAC7D,CAAC;AAGD,UAAQ,KAAK,wBAAwB,OAAO,SAAyB,UAAwB;AAC3F,UAAM,WAAW,eAAe,OAAO;AACvC,UAAM,OAAO,QAAQ;AACrB,UAAM,QAAS,KAA2B;AAE1C,UAAM,QAAQ,MAAM,QAAQ,wBAAwB,OAAO,QAAQ;AAEnE,QAAI,SAAS,qBAAqB;AAChC,YAAM,oBAAoB,OAAO,EAAE,OAAO,SAAS,CAAC;AAAA,IACtD;AAEA,WAAO,MAAM,OAAO,GAAG,EAAE,KAAK,EAAE,SAAS,yDAAyD,CAAC;AAAA,EACrG,CAAC;AACH;AAUO,SAAS,aAAiC,SAAyB,uBAAgC;AACxG,SAAO,OAAO,SAAyB,UAAwB;AAC7D,QAAI;AACF,YAAM,QAAQ,aAAa,SAAS,qBAAqB;AACzD,cAAQ,WAAW,MAAM,QAAQ,aAAa,KAAK;AAAA,IACrD,SAAS,OAAO;AACd,UAAI,iBAAiB,WAAW;AAC9B,eAAO,MACJ,OAAO,MAAM,UAAU,EACvB,KAAK,EAAE,OAAO,MAAM,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,MACvD;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AAQO,SAAS,YAAgC,YAA4B,OAAiB;AAC3F,QAAM,YAAY,QAAQ,UAAU,GAAG,KAAK;AAE5C,SAAO,OAAO,SAAyB,UAAwB;AAC7D,QAAI;AACF,YAAM,QAAQ,aAAa,OAAO;AAClC,YAAM,OAAO,MAAM,QAAQ,aAAa,KAAK;AAC7C,gBAAU,IAAI;AACd,cAAQ,WAAW;AAAA,IACrB,SAAS,OAAO;AACd,UAAI,iBAAiB,WAAW;AAC9B,eAAO,MACJ,OAAO,MAAM,UAAU,EACvB,KAAK,EAAE,OAAO,MAAM,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,MACvD;AACA,YAAM;AAAA,IACR;AAAA,EACF;AACF;AASO,SAAS,uBAA2C,SAAyB,iBAAyB;AAC3G,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA,IAKL,eAAe;AACb,aAAO,OAAO,SAAyB,UAAwB;AAC7D,YAAI;AACF,gBAAM,QAAQ,aAAa,SAAS,eAAe;AACnD,kBAAQ,WAAW,MAAM,QAAQ,aAAa,KAAK;AAAA,QACrD,SAAS,OAAO;AACd,cAAI,iBAAiB,WAAW;AAC9B,mBAAO,MACJ,OAAO,MAAM,UAAU,EACvB,KAAK,EAAE,OAAO,MAAM,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,UACvD;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,eAAe,OAAiB;AAC9B,YAAM,YAAY,QAAQ,UAAU,GAAG,KAAK;AAE5C,aAAO,OAAO,SAAyB,UAAwB;AAC7D,YAAI;AACF,gBAAM,QAAQ,aAAa,SAAS,eAAe;AACnD,gBAAM,OAAO,MAAM,QAAQ,aAAa,KAAK;AAC7C,oBAAU,IAAI;AACd,kBAAQ,WAAW;AAAA,QACrB,SAAS,OAAO;AACd,cAAI,iBAAiB,WAAW;AAC9B,mBAAO,MACJ,OAAO,MAAM,UAAU,EACvB,KAAK,EAAE,OAAO,MAAM,MAAM,SAAS,MAAM,QAAQ,CAAC;AAAA,UACvD;AACA,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forjakit/auth-fastify",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Fastify plugin that exposes `@forjakit/auth` as a REST API. Registers auth routes with tenant isolation, input validation, rate limiting, optional cookie-based token delivery, and structured error responses. Also provides standalone middleware factories f",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Giovani de Biagi Alves <giovani@giovanialves.dev>",
|