@peterbud/nuxt-aegis 1.1.0-alpha
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 +166 -0
- package/dist/module.d.mts +6 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +354 -0
- package/dist/runtime/app/composables/useAuth.d.ts +85 -0
- package/dist/runtime/app/composables/useAuth.js +187 -0
- package/dist/runtime/app/middleware/auth-logged-in.d.ts +16 -0
- package/dist/runtime/app/middleware/auth-logged-in.js +25 -0
- package/dist/runtime/app/middleware/auth-logged-out.d.ts +20 -0
- package/dist/runtime/app/middleware/auth-logged-out.js +17 -0
- package/dist/runtime/app/pages/AuthCallback.d.vue.ts +3 -0
- package/dist/runtime/app/pages/AuthCallback.vue +92 -0
- package/dist/runtime/app/pages/AuthCallback.vue.d.ts +3 -0
- package/dist/runtime/app/plugins/api.client.d.ts +11 -0
- package/dist/runtime/app/plugins/api.client.js +92 -0
- package/dist/runtime/app/plugins/api.server.d.ts +13 -0
- package/dist/runtime/app/plugins/api.server.js +28 -0
- package/dist/runtime/app/plugins/ssr-state.server.d.ts +2 -0
- package/dist/runtime/app/plugins/ssr-state.server.js +13 -0
- package/dist/runtime/app/router.options.d.ts +12 -0
- package/dist/runtime/app/router.options.js +11 -0
- package/dist/runtime/app/utils/logger.d.ts +18 -0
- package/dist/runtime/app/utils/logger.js +48 -0
- package/dist/runtime/app/utils/redirectValidation.d.ts +18 -0
- package/dist/runtime/app/utils/redirectValidation.js +21 -0
- package/dist/runtime/app/utils/routeMatching.d.ts +13 -0
- package/dist/runtime/app/utils/routeMatching.js +10 -0
- package/dist/runtime/app/utils/tokenStore.d.ts +24 -0
- package/dist/runtime/app/utils/tokenStore.js +14 -0
- package/dist/runtime/app/utils/tokenUtils.d.ts +17 -0
- package/dist/runtime/app/utils/tokenUtils.js +4 -0
- package/dist/runtime/server/middleware/auth.d.ts +6 -0
- package/dist/runtime/server/middleware/auth.js +82 -0
- package/dist/runtime/server/plugins/ssr-auth.d.ts +7 -0
- package/dist/runtime/server/plugins/ssr-auth.js +82 -0
- package/dist/runtime/server/providers/auth0.d.ts +12 -0
- package/dist/runtime/server/providers/auth0.js +57 -0
- package/dist/runtime/server/providers/github.d.ts +12 -0
- package/dist/runtime/server/providers/github.js +44 -0
- package/dist/runtime/server/providers/google.d.ts +12 -0
- package/dist/runtime/server/providers/google.js +46 -0
- package/dist/runtime/server/providers/mock.d.ts +37 -0
- package/dist/runtime/server/providers/mock.js +129 -0
- package/dist/runtime/server/providers/oauthBase.d.ts +72 -0
- package/dist/runtime/server/providers/oauthBase.js +183 -0
- package/dist/runtime/server/routes/impersonate.post.d.ts +21 -0
- package/dist/runtime/server/routes/impersonate.post.js +68 -0
- package/dist/runtime/server/routes/logout.post.d.ts +9 -0
- package/dist/runtime/server/routes/logout.post.js +24 -0
- package/dist/runtime/server/routes/me.get.d.ts +6 -0
- package/dist/runtime/server/routes/me.get.js +11 -0
- package/dist/runtime/server/routes/mock/authorize.get.d.ts +29 -0
- package/dist/runtime/server/routes/mock/authorize.get.js +103 -0
- package/dist/runtime/server/routes/mock/token.post.d.ts +31 -0
- package/dist/runtime/server/routes/mock/token.post.js +88 -0
- package/dist/runtime/server/routes/mock/userinfo.get.d.ts +27 -0
- package/dist/runtime/server/routes/mock/userinfo.get.js +59 -0
- package/dist/runtime/server/routes/password/change.post.d.ts +4 -0
- package/dist/runtime/server/routes/password/change.post.js +108 -0
- package/dist/runtime/server/routes/password/login-verify.get.d.ts +2 -0
- package/dist/runtime/server/routes/password/login-verify.get.js +79 -0
- package/dist/runtime/server/routes/password/login.post.d.ts +4 -0
- package/dist/runtime/server/routes/password/login.post.js +66 -0
- package/dist/runtime/server/routes/password/register-verify.get.d.ts +2 -0
- package/dist/runtime/server/routes/password/register-verify.get.js +86 -0
- package/dist/runtime/server/routes/password/register.post.d.ts +4 -0
- package/dist/runtime/server/routes/password/register.post.js +87 -0
- package/dist/runtime/server/routes/password/reset-complete.post.d.ts +4 -0
- package/dist/runtime/server/routes/password/reset-complete.post.js +75 -0
- package/dist/runtime/server/routes/password/reset-request.post.d.ts +5 -0
- package/dist/runtime/server/routes/password/reset-request.post.js +52 -0
- package/dist/runtime/server/routes/password/reset-verify.get.d.ts +2 -0
- package/dist/runtime/server/routes/password/reset-verify.get.js +50 -0
- package/dist/runtime/server/routes/refresh.post.d.ts +8 -0
- package/dist/runtime/server/routes/refresh.post.js +102 -0
- package/dist/runtime/server/routes/token.post.d.ts +28 -0
- package/dist/runtime/server/routes/token.post.js +90 -0
- package/dist/runtime/server/routes/unimpersonate.post.d.ts +16 -0
- package/dist/runtime/server/routes/unimpersonate.post.js +65 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/server/utils/auth.d.ts +94 -0
- package/dist/runtime/server/utils/auth.js +54 -0
- package/dist/runtime/server/utils/authCodeStore.d.ts +137 -0
- package/dist/runtime/server/utils/authCodeStore.js +123 -0
- package/dist/runtime/server/utils/cookies.d.ts +15 -0
- package/dist/runtime/server/utils/cookies.js +23 -0
- package/dist/runtime/server/utils/customClaims.d.ts +37 -0
- package/dist/runtime/server/utils/customClaims.js +45 -0
- package/dist/runtime/server/utils/handler.d.ts +77 -0
- package/dist/runtime/server/utils/handler.js +7 -0
- package/dist/runtime/server/utils/impersonation.d.ts +48 -0
- package/dist/runtime/server/utils/impersonation.js +259 -0
- package/dist/runtime/server/utils/jwt.d.ts +24 -0
- package/dist/runtime/server/utils/jwt.js +77 -0
- package/dist/runtime/server/utils/logger.d.ts +18 -0
- package/dist/runtime/server/utils/logger.js +49 -0
- package/dist/runtime/server/utils/magicCodeStore.d.ts +27 -0
- package/dist/runtime/server/utils/magicCodeStore.js +66 -0
- package/dist/runtime/server/utils/mockCodeStore.d.ts +89 -0
- package/dist/runtime/server/utils/mockCodeStore.js +71 -0
- package/dist/runtime/server/utils/password.d.ts +33 -0
- package/dist/runtime/server/utils/password.js +48 -0
- package/dist/runtime/server/utils/refreshToken.d.ts +74 -0
- package/dist/runtime/server/utils/refreshToken.js +108 -0
- package/dist/runtime/server/utils/resetSessionStore.d.ts +12 -0
- package/dist/runtime/server/utils/resetSessionStore.js +29 -0
- package/dist/runtime/tasks/cleanup/magic-codes.d.ts +10 -0
- package/dist/runtime/tasks/cleanup/magic-codes.js +79 -0
- package/dist/runtime/tasks/cleanup/refresh-tokens.d.ts +10 -0
- package/dist/runtime/tasks/cleanup/refresh-tokens.js +55 -0
- package/dist/runtime/tasks/cleanup/reset-sessions.d.ts +8 -0
- package/dist/runtime/tasks/cleanup/reset-sessions.js +45 -0
- package/dist/runtime/types/augmentation.d.ts +73 -0
- package/dist/runtime/types/augmentation.js +0 -0
- package/dist/runtime/types/authCode.d.ts +60 -0
- package/dist/runtime/types/authCode.js +0 -0
- package/dist/runtime/types/callbacks.d.ts +54 -0
- package/dist/runtime/types/callbacks.js +0 -0
- package/dist/runtime/types/config.d.ts +129 -0
- package/dist/runtime/types/config.js +0 -0
- package/dist/runtime/types/hooks.d.ts +118 -0
- package/dist/runtime/types/hooks.js +0 -0
- package/dist/runtime/types/index.d.ts +13 -0
- package/dist/runtime/types/index.js +1 -0
- package/dist/runtime/types/providers.d.ts +212 -0
- package/dist/runtime/types/providers.js +0 -0
- package/dist/runtime/types/refresh.d.ts +61 -0
- package/dist/runtime/types/refresh.js +0 -0
- package/dist/runtime/types/routes.d.ts +30 -0
- package/dist/runtime/types/routes.js +0 -0
- package/dist/runtime/types/token.d.ts +182 -0
- package/dist/runtime/types/token.js +0 -0
- package/dist/types.d.mts +7 -0
- package/package.json +80 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createLogger } from "./logger.js";
|
|
2
|
+
const logger = createLogger("RedirectValidation");
|
|
3
|
+
export function validateRedirectPath(path) {
|
|
4
|
+
if (!path || typeof path !== "string") {
|
|
5
|
+
const error = "Redirect path must be a non-empty string";
|
|
6
|
+
logger.error(error, { path });
|
|
7
|
+
throw new Error(error);
|
|
8
|
+
}
|
|
9
|
+
const trimmedPath = path.trim();
|
|
10
|
+
if (trimmedPath.startsWith("http://") || trimmedPath.startsWith("https://") || trimmedPath.startsWith("//")) {
|
|
11
|
+
const error = 'Redirect path must be a relative path, not an absolute URL. Use paths like "/dashboard" instead of full URLs.';
|
|
12
|
+
logger.error(error, { path: trimmedPath });
|
|
13
|
+
throw new Error(error);
|
|
14
|
+
}
|
|
15
|
+
if (!trimmedPath.startsWith("/")) {
|
|
16
|
+
const error = 'Redirect path must start with "/" (e.g., "/dashboard")';
|
|
17
|
+
logger.error(error, { path: trimmedPath });
|
|
18
|
+
throw new Error(error);
|
|
19
|
+
}
|
|
20
|
+
return trimmedPath;
|
|
21
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert glob pattern to regex for route matching
|
|
3
|
+
* @param pattern - Glob pattern (supports *, **, ?)
|
|
4
|
+
* @returns RegExp for testing paths
|
|
5
|
+
*/
|
|
6
|
+
export declare function globToRegex(pattern: string): RegExp;
|
|
7
|
+
/**
|
|
8
|
+
* Check if a path matches any of the provided route patterns
|
|
9
|
+
* @param path - The path to check
|
|
10
|
+
* @param patterns - Array of glob patterns
|
|
11
|
+
* @returns true if the path matches any pattern
|
|
12
|
+
*/
|
|
13
|
+
export declare function isRouteMatch(path: string, patterns: string[]): boolean;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function globToRegex(pattern) {
|
|
2
|
+
const regexPattern = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "___DOUBLE_STAR___").replace(/\*/g, "[^/]*").replace(/___DOUBLE_STAR___/g, ".*").replace(/\?/g, "[^/]");
|
|
3
|
+
return new RegExp(`^${regexPattern}$`);
|
|
4
|
+
}
|
|
5
|
+
export function isRouteMatch(path, patterns) {
|
|
6
|
+
return patterns.some((pattern) => {
|
|
7
|
+
const regex = globToRegex(pattern);
|
|
8
|
+
return regex.test(path);
|
|
9
|
+
});
|
|
10
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the current access token
|
|
3
|
+
* @returns The current access token or null if not authenticated
|
|
4
|
+
*/
|
|
5
|
+
export declare function getAccessToken(): string | null;
|
|
6
|
+
/**
|
|
7
|
+
* Set a new access token in memory
|
|
8
|
+
* CL-18: Store access token in memory as a reactive reference variable
|
|
9
|
+
*
|
|
10
|
+
* @param token - The access token to store
|
|
11
|
+
*/
|
|
12
|
+
export declare function setAccessToken(token: string | null): void;
|
|
13
|
+
/**
|
|
14
|
+
* Clear the access token from memory
|
|
15
|
+
* CL-19: Clear token on logout or session end
|
|
16
|
+
*/
|
|
17
|
+
export declare function clearAccessToken(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Get reactive reference to the token (for internal use)
|
|
20
|
+
* Useful when you need to watch for token changes
|
|
21
|
+
*
|
|
22
|
+
* @returns Reactive reference to the access token
|
|
23
|
+
*/
|
|
24
|
+
export declare function getAccessTokenRef(): import("vue").Ref<string | null, string | null>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ref } from "vue";
|
|
2
|
+
const accessToken = ref(null);
|
|
3
|
+
export function getAccessToken() {
|
|
4
|
+
return accessToken.value;
|
|
5
|
+
}
|
|
6
|
+
export function setAccessToken(token) {
|
|
7
|
+
accessToken.value = token;
|
|
8
|
+
}
|
|
9
|
+
export function clearAccessToken() {
|
|
10
|
+
setAccessToken(null);
|
|
11
|
+
}
|
|
12
|
+
export function getAccessTokenRef() {
|
|
13
|
+
return accessToken;
|
|
14
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { TokenPayload } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Filter out time-sensitive JWT metadata claims that cause hydration mismatches
|
|
4
|
+
*
|
|
5
|
+
* These claims are automatically regenerated on each token creation, causing different
|
|
6
|
+
* values between server-rendered and client-refreshed tokens. Filtering them prevents
|
|
7
|
+
* hydration mismatches while preserving stable user data.
|
|
8
|
+
*
|
|
9
|
+
* Filtered claims:
|
|
10
|
+
* - iat (issued at) - timestamp when token was created
|
|
11
|
+
* - exp (expiration) - timestamp when token expires
|
|
12
|
+
* - iss (issuer) - token issuer (constant but not user data)
|
|
13
|
+
*
|
|
14
|
+
* @param user - Token payload with all claims
|
|
15
|
+
* @returns Token payload with only stable user data
|
|
16
|
+
*/
|
|
17
|
+
export declare function filterTimeSensitiveClaims(user: TokenPayload): TokenPayload;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { defineEventHandler, createError, getRequestURL, getHeader } from "h3";
|
|
2
|
+
import { getRouteRules, useRuntimeConfig } from "#imports";
|
|
3
|
+
import { verifyToken } from "../utils/jwt.js";
|
|
4
|
+
import { createLogger } from "../utils/logger.js";
|
|
5
|
+
const logger = createLogger("Middleware");
|
|
6
|
+
export default defineEventHandler(async (event) => {
|
|
7
|
+
const config = useRuntimeConfig();
|
|
8
|
+
const requestURL = getRequestURL(event);
|
|
9
|
+
logger.debug("Auth middleware triggered for URL:", requestURL.pathname);
|
|
10
|
+
const tokenConfig = config.nuxtAegis?.token;
|
|
11
|
+
const authPath = config.public.nuxtAegis.authPath;
|
|
12
|
+
if (requestURL.pathname.startsWith(authPath)) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
if (requestURL.pathname.startsWith("/_nuxt/") || requestURL.pathname.startsWith("/api/_")) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const routeRules = await getRouteRules(event);
|
|
19
|
+
const authConfig = routeRules.nuxtAegis?.auth;
|
|
20
|
+
const shouldProtect = authConfig === true || authConfig === "required" || authConfig === "protected";
|
|
21
|
+
const shouldSkip = authConfig === false || authConfig === "public" || authConfig === "skip";
|
|
22
|
+
if (!shouldProtect || shouldSkip) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
let token;
|
|
26
|
+
const authHeader = getHeader(event, "authorization");
|
|
27
|
+
if (authHeader?.startsWith("Bearer ")) {
|
|
28
|
+
token = authHeader.substring(7);
|
|
29
|
+
}
|
|
30
|
+
if (!token) {
|
|
31
|
+
throw createError({
|
|
32
|
+
statusCode: 401,
|
|
33
|
+
statusMessage: "Unauthorized",
|
|
34
|
+
message: "Authentication required"
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
if (!tokenConfig || !tokenConfig.secret) {
|
|
38
|
+
logger.error("Token configuration is missing");
|
|
39
|
+
throw createError({
|
|
40
|
+
statusCode: 500,
|
|
41
|
+
statusMessage: "Internal Server Error",
|
|
42
|
+
message: "Authentication configuration error"
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const payload = await verifyToken(token, tokenConfig.secret);
|
|
46
|
+
if (!payload) {
|
|
47
|
+
logger.debug("Token verification failed for path:", requestURL.pathname);
|
|
48
|
+
throw createError({
|
|
49
|
+
statusCode: 401,
|
|
50
|
+
statusMessage: "Unauthorized",
|
|
51
|
+
message: "Invalid or expired token"
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (tokenConfig.issuer && payload.iss !== tokenConfig.issuer) {
|
|
55
|
+
logger.debug("Token issuer mismatch. Expected:", tokenConfig.issuer, "Got:", payload.iss);
|
|
56
|
+
throw createError({
|
|
57
|
+
statusCode: 401,
|
|
58
|
+
statusMessage: "Unauthorized",
|
|
59
|
+
message: "Invalid token issuer"
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
if (tokenConfig.audience && payload.aud) {
|
|
63
|
+
const audienceMatch = Array.isArray(payload.aud) ? payload.aud.includes(tokenConfig.audience) : payload.aud === tokenConfig.audience;
|
|
64
|
+
if (!audienceMatch) {
|
|
65
|
+
logger.debug("Token audience mismatch. Expected:", tokenConfig.audience, "Got:", payload.aud);
|
|
66
|
+
throw createError({
|
|
67
|
+
statusCode: 401,
|
|
68
|
+
statusMessage: "Unauthorized",
|
|
69
|
+
message: "Invalid token audience"
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
const { iat, exp, iss, aud, ...userData } = payload;
|
|
74
|
+
event.context.user = userData;
|
|
75
|
+
if (payload.impersonation) {
|
|
76
|
+
event.context.originalUser = {
|
|
77
|
+
sub: payload.impersonation.originalUserId,
|
|
78
|
+
email: payload.impersonation.originalUserEmail,
|
|
79
|
+
name: payload.impersonation.originalUserName
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nitro server plugin for SSR authentication
|
|
3
|
+
* Validates refresh token cookie and generates short-lived access tokens
|
|
4
|
+
* for authenticated server-side rendering without rotating the refresh token
|
|
5
|
+
*/
|
|
6
|
+
declare const _default: import("nitropack").NitroAppPlugin;
|
|
7
|
+
export default _default;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { defineNitroPlugin } from "nitropack/runtime";
|
|
2
|
+
import { getCookie } from "h3";
|
|
3
|
+
import { hashRefreshToken, getRefreshTokenData } from "../utils/refreshToken.js";
|
|
4
|
+
import { generateToken } from "../utils/jwt.js";
|
|
5
|
+
import { processCustomClaims } from "../utils/customClaims.js";
|
|
6
|
+
import { useRuntimeConfig } from "#imports";
|
|
7
|
+
import { createLogger } from "../utils/logger.js";
|
|
8
|
+
const logger = createLogger("SSR:Auth");
|
|
9
|
+
export default defineNitroPlugin((nitroApp) => {
|
|
10
|
+
nitroApp.hooks.hook("request", async (event) => {
|
|
11
|
+
const config = useRuntimeConfig(event);
|
|
12
|
+
const authPath = config.public.nuxtAegis?.authPath || "/auth";
|
|
13
|
+
const requestPath = event.node?.req?.url || event.path || "";
|
|
14
|
+
if (requestPath.startsWith(authPath) || requestPath.startsWith("/_nuxt/") || requestPath.startsWith("/api/") || requestPath.includes("/favicon.ico") || requestPath.includes("/__")) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
logger.debug("SSR auth hook triggered for URL:", requestPath);
|
|
18
|
+
if (event.context.user) {
|
|
19
|
+
logger.debug("SSR auth skipped: user already authenticated in context");
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
if (!config.public.nuxtAegis?.enableSSR) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const startTime = performance.now();
|
|
26
|
+
const cookieConfig = config.nuxtAegis?.tokenRefresh?.cookie;
|
|
27
|
+
const tokenConfig = config.nuxtAegis?.token;
|
|
28
|
+
const tokenRefreshConfig = config.nuxtAegis?.tokenRefresh;
|
|
29
|
+
if (!tokenConfig?.secret) {
|
|
30
|
+
logger.debug("SSR auth skipped: token secret not configured");
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
const cookieName = cookieConfig?.cookieName || "nuxt-aegis-refresh";
|
|
34
|
+
const refreshToken = getCookie(event, cookieName);
|
|
35
|
+
if (!refreshToken) {
|
|
36
|
+
logger.debug("SSR auth skipped: no refresh token cookie found");
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const hashedToken = hashRefreshToken(refreshToken);
|
|
41
|
+
const storedRefreshToken = await getRefreshTokenData(hashedToken, event);
|
|
42
|
+
const isRevoked = storedRefreshToken?.isRevoked || false;
|
|
43
|
+
const isExpired = storedRefreshToken?.expiresAt ? Date.now() > storedRefreshToken.expiresAt : true;
|
|
44
|
+
if (!storedRefreshToken || isRevoked || isExpired) {
|
|
45
|
+
logger.debug("SSR auth skipped: refresh token invalid, revoked, or expired");
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const providerUserInfo = storedRefreshToken.providerUserInfo;
|
|
49
|
+
const provider = storedRefreshToken.provider;
|
|
50
|
+
const userPayload = {
|
|
51
|
+
sub: String(providerUserInfo.sub || providerUserInfo.email || providerUserInfo.id || ""),
|
|
52
|
+
email: providerUserInfo.email,
|
|
53
|
+
name: providerUserInfo.name,
|
|
54
|
+
picture: providerUserInfo.picture,
|
|
55
|
+
provider
|
|
56
|
+
};
|
|
57
|
+
let customClaims = {};
|
|
58
|
+
const providerConfig = config.nuxtAegis?.providers?.[provider];
|
|
59
|
+
if (providerConfig && "customClaims" in providerConfig && providerConfig.customClaims) {
|
|
60
|
+
customClaims = await processCustomClaims(
|
|
61
|
+
providerUserInfo,
|
|
62
|
+
providerConfig.customClaims
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
const ssrTokenExpiry = tokenRefreshConfig?.ssrTokenExpiry || "5m";
|
|
66
|
+
const ssrAccessToken = await generateToken(
|
|
67
|
+
userPayload,
|
|
68
|
+
{
|
|
69
|
+
...tokenConfig,
|
|
70
|
+
expiresIn: ssrTokenExpiry
|
|
71
|
+
},
|
|
72
|
+
customClaims
|
|
73
|
+
);
|
|
74
|
+
event.context.ssrAccessToken = ssrAccessToken;
|
|
75
|
+
event.context.user = userPayload;
|
|
76
|
+
const duration = Math.round(performance.now() - startTime);
|
|
77
|
+
logger.debug(`SSR auth completed in ${duration}ms for user: ${userPayload.email}`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.error("SSR auth failed:", error);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OAuthConfig, Auth0ProviderConfig } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create an Auth0 OAuth event handler
|
|
4
|
+
* @param options - Configuration object
|
|
5
|
+
* @param options.config - Auth0 OAuth provider configuration
|
|
6
|
+
* @param options.onError - Error callback function
|
|
7
|
+
* @param options.customClaims - Custom claims to add to JWT
|
|
8
|
+
* @param options.onUserInfo - User transformation hook
|
|
9
|
+
* @param options.onSuccess - Success callback hook
|
|
10
|
+
* @returns Event handler for Auth0 OAuth authentication
|
|
11
|
+
*/
|
|
12
|
+
export declare function defineOAuthAuth0EventHandler({ config, onError, customClaims, onUserInfo, onSuccess, }: OAuthConfig<Auth0ProviderConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { defineOAuthEventHandler, defineOAuthProvider, validateAuthorizationParams } from "./oauthBase.js";
|
|
2
|
+
function getAuth0DomainUrl(domain) {
|
|
3
|
+
return domain.startsWith("https://") ? domain : `https://${domain}`;
|
|
4
|
+
}
|
|
5
|
+
const auth0Implementation = defineOAuthProvider({
|
|
6
|
+
runtimeConfigKey: "auth0",
|
|
7
|
+
defaultConfig: {
|
|
8
|
+
scopes: ["openid", "profile", "email"]
|
|
9
|
+
},
|
|
10
|
+
// These will be dynamically set based on the domain in buildAuthQuery
|
|
11
|
+
authorizeUrl: "",
|
|
12
|
+
tokenUrl: "",
|
|
13
|
+
userInfoUrl: "",
|
|
14
|
+
extractUser: (userResponse) => userResponse,
|
|
15
|
+
buildAuthQuery: (config, redirectUri, state) => {
|
|
16
|
+
if (!config.domain) {
|
|
17
|
+
throw new Error("Auth0 domain is required in configuration");
|
|
18
|
+
}
|
|
19
|
+
const domainUrl = getAuth0DomainUrl(config.domain);
|
|
20
|
+
auth0Implementation.authorizeUrl = config.authorizeUrl || `${domainUrl}/authorize`;
|
|
21
|
+
auth0Implementation.tokenUrl = config.tokenUrl || `${domainUrl}/oauth/token`;
|
|
22
|
+
auth0Implementation.userInfoUrl = config.userInfoUrl || `${domainUrl}/userinfo`;
|
|
23
|
+
const customParams = validateAuthorizationParams(config.authorizationParams, "auth0");
|
|
24
|
+
return {
|
|
25
|
+
// Custom parameters first (can be overridden by defaults)
|
|
26
|
+
...customParams,
|
|
27
|
+
// Default OAuth parameters (take precedence)
|
|
28
|
+
response_type: "code",
|
|
29
|
+
client_id: config.clientId,
|
|
30
|
+
redirect_uri: redirectUri,
|
|
31
|
+
scope: config.scopes?.join(" ") || "openid profile email",
|
|
32
|
+
state: state || ""
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
buildTokenBody: (config, code, redirectUri) => ({
|
|
36
|
+
code,
|
|
37
|
+
client_id: config.clientId,
|
|
38
|
+
client_secret: config.clientSecret,
|
|
39
|
+
redirect_uri: redirectUri,
|
|
40
|
+
grant_type: "authorization_code"
|
|
41
|
+
})
|
|
42
|
+
});
|
|
43
|
+
export function defineOAuthAuth0EventHandler({
|
|
44
|
+
config = {},
|
|
45
|
+
onError,
|
|
46
|
+
customClaims,
|
|
47
|
+
onUserInfo,
|
|
48
|
+
onSuccess
|
|
49
|
+
}) {
|
|
50
|
+
return defineOAuthEventHandler(auth0Implementation, {
|
|
51
|
+
config,
|
|
52
|
+
onError,
|
|
53
|
+
customClaims,
|
|
54
|
+
onUserInfo,
|
|
55
|
+
onSuccess
|
|
56
|
+
});
|
|
57
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OAuthConfig, GithubProviderConfig } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a GitHub OAuth event handler
|
|
4
|
+
* @param options - Configuration object
|
|
5
|
+
* @param options.config - GitHub OAuth provider configuration
|
|
6
|
+
* @param options.onError - Error callback function
|
|
7
|
+
* @param options.customClaims - Custom claims to add to JWT
|
|
8
|
+
* @param options.onUserInfo - User transformation hook
|
|
9
|
+
* @param options.onSuccess - Success callback hook
|
|
10
|
+
* @returns Event handler for GitHub OAuth authentication
|
|
11
|
+
*/
|
|
12
|
+
export declare function defineOAuthGithubEventHandler({ config, onError, customClaims, onUserInfo, onSuccess, }: OAuthConfig<GithubProviderConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { defineOAuthEventHandler, defineOAuthProvider, validateAuthorizationParams } from "./oauthBase.js";
|
|
2
|
+
const githubImplementation = defineOAuthProvider({
|
|
3
|
+
runtimeConfigKey: "github",
|
|
4
|
+
defaultConfig: {
|
|
5
|
+
scopes: ["user:email"]
|
|
6
|
+
},
|
|
7
|
+
authorizeUrl: "https://github.com/login/oauth/authorize",
|
|
8
|
+
tokenUrl: "https://github.com/login/oauth/access_token",
|
|
9
|
+
userInfoUrl: "https://api.github.com/user",
|
|
10
|
+
extractUser: (userResponse) => userResponse,
|
|
11
|
+
buildAuthQuery: (config, redirectUri, state) => {
|
|
12
|
+
const customParams = validateAuthorizationParams(config.authorizationParams, "github");
|
|
13
|
+
return {
|
|
14
|
+
// Custom parameters first (can be overridden by defaults)
|
|
15
|
+
...customParams,
|
|
16
|
+
// Default OAuth parameters (take precedence)
|
|
17
|
+
client_id: config.clientId,
|
|
18
|
+
redirect_uri: redirectUri,
|
|
19
|
+
scope: config.scopes?.join(" ") || "user:email",
|
|
20
|
+
state: state || ""
|
|
21
|
+
};
|
|
22
|
+
},
|
|
23
|
+
buildTokenBody: (config, code, redirectUri) => ({
|
|
24
|
+
code,
|
|
25
|
+
client_id: config.clientId,
|
|
26
|
+
client_secret: config.clientSecret,
|
|
27
|
+
redirect_uri: redirectUri
|
|
28
|
+
})
|
|
29
|
+
});
|
|
30
|
+
export function defineOAuthGithubEventHandler({
|
|
31
|
+
config,
|
|
32
|
+
onError,
|
|
33
|
+
customClaims,
|
|
34
|
+
onUserInfo,
|
|
35
|
+
onSuccess
|
|
36
|
+
}) {
|
|
37
|
+
return defineOAuthEventHandler(githubImplementation, {
|
|
38
|
+
config,
|
|
39
|
+
onError,
|
|
40
|
+
customClaims,
|
|
41
|
+
onUserInfo,
|
|
42
|
+
onSuccess
|
|
43
|
+
});
|
|
44
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { OAuthConfig, GoogleProviderConfig } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a Google OAuth event handler
|
|
4
|
+
* @param options - Configuration object
|
|
5
|
+
* @param options.config - Google OAuth provider configuration
|
|
6
|
+
* @param options.onError - Error callback function
|
|
7
|
+
* @param options.customClaims - Custom claims to add to JWT
|
|
8
|
+
* @param options.onUserInfo - User transformation hook
|
|
9
|
+
* @param options.onSuccess - Success callback hook
|
|
10
|
+
* @returns Event handler for Google OAuth authentication
|
|
11
|
+
*/
|
|
12
|
+
export declare function defineOAuthGoogleEventHandler({ config, onError, customClaims, onUserInfo, onSuccess, }: OAuthConfig<GoogleProviderConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { defineOAuthEventHandler, defineOAuthProvider, validateAuthorizationParams } from "./oauthBase.js";
|
|
2
|
+
const googleImplementation = defineOAuthProvider({
|
|
3
|
+
runtimeConfigKey: "google",
|
|
4
|
+
defaultConfig: {
|
|
5
|
+
scopes: ["openid", "profile", "email"]
|
|
6
|
+
},
|
|
7
|
+
authorizeUrl: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
8
|
+
tokenUrl: "https://oauth2.googleapis.com/token",
|
|
9
|
+
userInfoUrl: "https://www.googleapis.com/oauth2/v3/userinfo",
|
|
10
|
+
extractUser: (userResponse) => userResponse,
|
|
11
|
+
buildAuthQuery: (config, redirectUri, state) => {
|
|
12
|
+
const customParams = validateAuthorizationParams(config.authorizationParams, "google");
|
|
13
|
+
return {
|
|
14
|
+
// Custom parameters first (can be overridden by defaults)
|
|
15
|
+
...customParams,
|
|
16
|
+
// Default OAuth parameters (take precedence)
|
|
17
|
+
response_type: "code",
|
|
18
|
+
client_id: config.clientId,
|
|
19
|
+
redirect_uri: redirectUri,
|
|
20
|
+
scope: config.scopes?.join(" ") || "openid profile email",
|
|
21
|
+
state: state || ""
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
buildTokenBody: (config, code, redirectUri) => ({
|
|
25
|
+
code,
|
|
26
|
+
client_id: config.clientId,
|
|
27
|
+
client_secret: config.clientSecret,
|
|
28
|
+
redirect_uri: redirectUri,
|
|
29
|
+
grant_type: "authorization_code"
|
|
30
|
+
})
|
|
31
|
+
});
|
|
32
|
+
export function defineOAuthGoogleEventHandler({
|
|
33
|
+
config,
|
|
34
|
+
onError,
|
|
35
|
+
customClaims,
|
|
36
|
+
onUserInfo,
|
|
37
|
+
onSuccess
|
|
38
|
+
}) {
|
|
39
|
+
return defineOAuthEventHandler(googleImplementation, {
|
|
40
|
+
config,
|
|
41
|
+
onError,
|
|
42
|
+
customClaims,
|
|
43
|
+
onUserInfo,
|
|
44
|
+
onSuccess
|
|
45
|
+
});
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { OAuthConfig, MockProviderConfig } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Create a Mock OAuth event handler
|
|
4
|
+
*
|
|
5
|
+
* Wraps the OAuth handler to:
|
|
6
|
+
* 1. Check if mock provider is allowed (blocks production)
|
|
7
|
+
* 2. Validate configuration
|
|
8
|
+
* 3. Dynamically set base URL for mock endpoints
|
|
9
|
+
* 4. Pass through user selection and error simulation parameters
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* ```typescript
|
|
13
|
+
* // server/routes/auth/mock.get.ts
|
|
14
|
+
* export default defineOAuthMockEventHandler({
|
|
15
|
+
* customClaims: {
|
|
16
|
+
* role: 'user',
|
|
17
|
+
* permissions: ['read', 'write'],
|
|
18
|
+
* },
|
|
19
|
+
* })
|
|
20
|
+
* ```
|
|
21
|
+
*
|
|
22
|
+
* User Selection:
|
|
23
|
+
* - Navigate to /auth/mock to use default user
|
|
24
|
+
* - Navigate to /auth/mock?user=admin to use specific persona
|
|
25
|
+
*
|
|
26
|
+
* Error Simulation:
|
|
27
|
+
* - Navigate to /auth/mock?mock_error=access_denied to test error handling
|
|
28
|
+
*
|
|
29
|
+
* @param options - Configuration object
|
|
30
|
+
* @param options.config - Mock provider configuration (optional, uses runtime config)
|
|
31
|
+
* @param options.onError - Error callback function
|
|
32
|
+
* @param options.customClaims - Custom claims to add to JWT
|
|
33
|
+
* @param options.onUserInfo - User transformation hook
|
|
34
|
+
* @param options.onSuccess - Success callback hook
|
|
35
|
+
* @returns Event handler for Mock OAuth authentication
|
|
36
|
+
*/
|
|
37
|
+
export declare function defineOAuthMockEventHandler({ config, onError, customClaims, onUserInfo, onSuccess, }: OAuthConfig<MockProviderConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { eventHandler, getRequestURL } from "h3";
|
|
2
|
+
import { defineOAuthEventHandler, defineOAuthProvider, validateAuthorizationParams } from "./oauthBase.js";
|
|
3
|
+
import { useRuntimeConfig } from "#imports";
|
|
4
|
+
import { createLogger } from "../utils/logger.js";
|
|
5
|
+
const logger = createLogger("MockProvider");
|
|
6
|
+
let hasLoggedWarning = false;
|
|
7
|
+
function checkMockProviderAllowed(config) {
|
|
8
|
+
const isProduction = process.env.NODE_ENV === "production";
|
|
9
|
+
const isTest = process.env.VITEST === "true" || process.env.NODE_ENV === "test";
|
|
10
|
+
if (isTest) {
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
if (isProduction && !config.enableInProduction) {
|
|
14
|
+
throw new Error(
|
|
15
|
+
"[nuxt-aegis] Mock provider is not available in production. This is a security feature. Never use mock authentication in production. If you absolutely must enable it (NOT RECOMMENDED), set enableInProduction: true in your mock provider config."
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
if (isProduction && config.enableInProduction) {
|
|
19
|
+
if (!hasLoggedWarning) {
|
|
20
|
+
logger.error(
|
|
21
|
+
"\u26A0\uFE0F Mock provider is enabled in PRODUCTION mode. This is extremely dangerous and should never be done in a real production environment!"
|
|
22
|
+
);
|
|
23
|
+
hasLoggedWarning = true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (!isProduction && !hasLoggedWarning) {
|
|
27
|
+
logger.warn(
|
|
28
|
+
"\u26A0\uFE0F Mock authentication provider is active. This is for development/testing only and should never be used in production."
|
|
29
|
+
);
|
|
30
|
+
hasLoggedWarning = true;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function validateMockConfig(config) {
|
|
34
|
+
if (!config.mockUsers || Object.keys(config.mockUsers).length === 0) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
'[nuxt-aegis] Mock provider requires mockUsers configuration. Define at least one user persona in your nuxt.config.ts:\n\nnuxtAegis: {\n providers: {\n mock: {\n clientId: "mock-client",\n clientSecret: "mock-secret",\n mockUsers: {\n user: {\n sub: "mock-user-001",\n email: "user@example.com",\n name: "Test User"\n }\n }\n }\n }\n}'
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
for (const [userId, userData] of Object.entries(config.mockUsers)) {
|
|
40
|
+
if (!userData.sub) {
|
|
41
|
+
throw new Error(`[nuxt-aegis] Mock user '${userId}' is missing required field: sub`);
|
|
42
|
+
}
|
|
43
|
+
if (!userData.email) {
|
|
44
|
+
throw new Error(`[nuxt-aegis] Mock user '${userId}' is missing required field: email`);
|
|
45
|
+
}
|
|
46
|
+
if (!userData.name) {
|
|
47
|
+
throw new Error(`[nuxt-aegis] Mock user '${userId}' is missing required field: name`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (config.defaultUser && !config.mockUsers[config.defaultUser]) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`[nuxt-aegis] Mock provider defaultUser '${config.defaultUser}' does not exist in mockUsers. Available users: ${Object.keys(config.mockUsers).join(", ")}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
const mockImplementation = defineOAuthProvider({
|
|
57
|
+
runtimeConfigKey: "mock",
|
|
58
|
+
defaultConfig: {
|
|
59
|
+
scopes: ["openid", "profile", "email"]
|
|
60
|
+
},
|
|
61
|
+
// These will be overridden dynamically with the actual server URL
|
|
62
|
+
authorizeUrl: "__MOCK_BASE_URL__/auth/mock/authorize",
|
|
63
|
+
tokenUrl: "__MOCK_BASE_URL__/auth/mock/token",
|
|
64
|
+
userInfoUrl: "__MOCK_BASE_URL__/auth/mock/userinfo",
|
|
65
|
+
extractUser: (userResponse) => userResponse,
|
|
66
|
+
buildAuthQuery: (config, redirectUri, state) => {
|
|
67
|
+
const customParams = validateAuthorizationParams(config.authorizationParams, "mock");
|
|
68
|
+
return {
|
|
69
|
+
// Custom parameters first (can be overridden by defaults)
|
|
70
|
+
...customParams,
|
|
71
|
+
// Default OAuth parameters (take precedence)
|
|
72
|
+
response_type: "code",
|
|
73
|
+
client_id: config.clientId,
|
|
74
|
+
redirect_uri: redirectUri,
|
|
75
|
+
scope: config.scopes?.join(" ") || "openid profile email",
|
|
76
|
+
state: state || ""
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
buildTokenBody: (config, code, redirectUri) => ({
|
|
80
|
+
code,
|
|
81
|
+
client_id: config.clientId,
|
|
82
|
+
client_secret: config.clientSecret,
|
|
83
|
+
redirect_uri: redirectUri,
|
|
84
|
+
grant_type: "authorization_code"
|
|
85
|
+
})
|
|
86
|
+
});
|
|
87
|
+
export function defineOAuthMockEventHandler({
|
|
88
|
+
config,
|
|
89
|
+
onError,
|
|
90
|
+
customClaims,
|
|
91
|
+
onUserInfo,
|
|
92
|
+
onSuccess
|
|
93
|
+
}) {
|
|
94
|
+
return eventHandler(async (event) => {
|
|
95
|
+
const requestURL = getRequestURL(event);
|
|
96
|
+
const baseUrl = `${requestURL.protocol}//${requestURL.host}`;
|
|
97
|
+
const { getQuery } = await import("h3");
|
|
98
|
+
const incomingQuery = getQuery(event);
|
|
99
|
+
const userParam = incomingQuery.user;
|
|
100
|
+
const mockErrorParam = incomingQuery.mock_error;
|
|
101
|
+
mockImplementation.authorizeUrl = `${baseUrl}/auth/mock/authorize`;
|
|
102
|
+
mockImplementation.tokenUrl = `${baseUrl}/auth/mock/token`;
|
|
103
|
+
mockImplementation.userInfoUrl = `${baseUrl}/auth/mock/userinfo`;
|
|
104
|
+
const runtimeConfig = useRuntimeConfig(event);
|
|
105
|
+
const mockConfig = runtimeConfig.nuxtAegis?.providers?.mock;
|
|
106
|
+
if (mockConfig) {
|
|
107
|
+
checkMockProviderAllowed(mockConfig);
|
|
108
|
+
validateMockConfig(mockConfig);
|
|
109
|
+
}
|
|
110
|
+
const enhancedConfig = config || mockConfig;
|
|
111
|
+
if (enhancedConfig && (userParam || mockErrorParam)) {
|
|
112
|
+
const additionalParams = {};
|
|
113
|
+
if (userParam) additionalParams.user = userParam;
|
|
114
|
+
if (mockErrorParam) additionalParams.mock_error = mockErrorParam;
|
|
115
|
+
enhancedConfig.authorizationParams = {
|
|
116
|
+
...enhancedConfig.authorizationParams,
|
|
117
|
+
...additionalParams
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
const oauthHandler = defineOAuthEventHandler(mockImplementation, {
|
|
121
|
+
config: enhancedConfig,
|
|
122
|
+
onError,
|
|
123
|
+
customClaims,
|
|
124
|
+
onUserInfo,
|
|
125
|
+
onSuccess
|
|
126
|
+
});
|
|
127
|
+
return oauthHandler(event);
|
|
128
|
+
});
|
|
129
|
+
}
|