@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.
Files changed (134) hide show
  1. package/README.md +166 -0
  2. package/dist/module.d.mts +6 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +354 -0
  5. package/dist/runtime/app/composables/useAuth.d.ts +85 -0
  6. package/dist/runtime/app/composables/useAuth.js +187 -0
  7. package/dist/runtime/app/middleware/auth-logged-in.d.ts +16 -0
  8. package/dist/runtime/app/middleware/auth-logged-in.js +25 -0
  9. package/dist/runtime/app/middleware/auth-logged-out.d.ts +20 -0
  10. package/dist/runtime/app/middleware/auth-logged-out.js +17 -0
  11. package/dist/runtime/app/pages/AuthCallback.d.vue.ts +3 -0
  12. package/dist/runtime/app/pages/AuthCallback.vue +92 -0
  13. package/dist/runtime/app/pages/AuthCallback.vue.d.ts +3 -0
  14. package/dist/runtime/app/plugins/api.client.d.ts +11 -0
  15. package/dist/runtime/app/plugins/api.client.js +92 -0
  16. package/dist/runtime/app/plugins/api.server.d.ts +13 -0
  17. package/dist/runtime/app/plugins/api.server.js +28 -0
  18. package/dist/runtime/app/plugins/ssr-state.server.d.ts +2 -0
  19. package/dist/runtime/app/plugins/ssr-state.server.js +13 -0
  20. package/dist/runtime/app/router.options.d.ts +12 -0
  21. package/dist/runtime/app/router.options.js +11 -0
  22. package/dist/runtime/app/utils/logger.d.ts +18 -0
  23. package/dist/runtime/app/utils/logger.js +48 -0
  24. package/dist/runtime/app/utils/redirectValidation.d.ts +18 -0
  25. package/dist/runtime/app/utils/redirectValidation.js +21 -0
  26. package/dist/runtime/app/utils/routeMatching.d.ts +13 -0
  27. package/dist/runtime/app/utils/routeMatching.js +10 -0
  28. package/dist/runtime/app/utils/tokenStore.d.ts +24 -0
  29. package/dist/runtime/app/utils/tokenStore.js +14 -0
  30. package/dist/runtime/app/utils/tokenUtils.d.ts +17 -0
  31. package/dist/runtime/app/utils/tokenUtils.js +4 -0
  32. package/dist/runtime/server/middleware/auth.d.ts +6 -0
  33. package/dist/runtime/server/middleware/auth.js +82 -0
  34. package/dist/runtime/server/plugins/ssr-auth.d.ts +7 -0
  35. package/dist/runtime/server/plugins/ssr-auth.js +82 -0
  36. package/dist/runtime/server/providers/auth0.d.ts +12 -0
  37. package/dist/runtime/server/providers/auth0.js +57 -0
  38. package/dist/runtime/server/providers/github.d.ts +12 -0
  39. package/dist/runtime/server/providers/github.js +44 -0
  40. package/dist/runtime/server/providers/google.d.ts +12 -0
  41. package/dist/runtime/server/providers/google.js +46 -0
  42. package/dist/runtime/server/providers/mock.d.ts +37 -0
  43. package/dist/runtime/server/providers/mock.js +129 -0
  44. package/dist/runtime/server/providers/oauthBase.d.ts +72 -0
  45. package/dist/runtime/server/providers/oauthBase.js +183 -0
  46. package/dist/runtime/server/routes/impersonate.post.d.ts +21 -0
  47. package/dist/runtime/server/routes/impersonate.post.js +68 -0
  48. package/dist/runtime/server/routes/logout.post.d.ts +9 -0
  49. package/dist/runtime/server/routes/logout.post.js +24 -0
  50. package/dist/runtime/server/routes/me.get.d.ts +6 -0
  51. package/dist/runtime/server/routes/me.get.js +11 -0
  52. package/dist/runtime/server/routes/mock/authorize.get.d.ts +29 -0
  53. package/dist/runtime/server/routes/mock/authorize.get.js +103 -0
  54. package/dist/runtime/server/routes/mock/token.post.d.ts +31 -0
  55. package/dist/runtime/server/routes/mock/token.post.js +88 -0
  56. package/dist/runtime/server/routes/mock/userinfo.get.d.ts +27 -0
  57. package/dist/runtime/server/routes/mock/userinfo.get.js +59 -0
  58. package/dist/runtime/server/routes/password/change.post.d.ts +4 -0
  59. package/dist/runtime/server/routes/password/change.post.js +108 -0
  60. package/dist/runtime/server/routes/password/login-verify.get.d.ts +2 -0
  61. package/dist/runtime/server/routes/password/login-verify.get.js +79 -0
  62. package/dist/runtime/server/routes/password/login.post.d.ts +4 -0
  63. package/dist/runtime/server/routes/password/login.post.js +66 -0
  64. package/dist/runtime/server/routes/password/register-verify.get.d.ts +2 -0
  65. package/dist/runtime/server/routes/password/register-verify.get.js +86 -0
  66. package/dist/runtime/server/routes/password/register.post.d.ts +4 -0
  67. package/dist/runtime/server/routes/password/register.post.js +87 -0
  68. package/dist/runtime/server/routes/password/reset-complete.post.d.ts +4 -0
  69. package/dist/runtime/server/routes/password/reset-complete.post.js +75 -0
  70. package/dist/runtime/server/routes/password/reset-request.post.d.ts +5 -0
  71. package/dist/runtime/server/routes/password/reset-request.post.js +52 -0
  72. package/dist/runtime/server/routes/password/reset-verify.get.d.ts +2 -0
  73. package/dist/runtime/server/routes/password/reset-verify.get.js +50 -0
  74. package/dist/runtime/server/routes/refresh.post.d.ts +8 -0
  75. package/dist/runtime/server/routes/refresh.post.js +102 -0
  76. package/dist/runtime/server/routes/token.post.d.ts +28 -0
  77. package/dist/runtime/server/routes/token.post.js +90 -0
  78. package/dist/runtime/server/routes/unimpersonate.post.d.ts +16 -0
  79. package/dist/runtime/server/routes/unimpersonate.post.js +65 -0
  80. package/dist/runtime/server/tsconfig.json +3 -0
  81. package/dist/runtime/server/utils/auth.d.ts +94 -0
  82. package/dist/runtime/server/utils/auth.js +54 -0
  83. package/dist/runtime/server/utils/authCodeStore.d.ts +137 -0
  84. package/dist/runtime/server/utils/authCodeStore.js +123 -0
  85. package/dist/runtime/server/utils/cookies.d.ts +15 -0
  86. package/dist/runtime/server/utils/cookies.js +23 -0
  87. package/dist/runtime/server/utils/customClaims.d.ts +37 -0
  88. package/dist/runtime/server/utils/customClaims.js +45 -0
  89. package/dist/runtime/server/utils/handler.d.ts +77 -0
  90. package/dist/runtime/server/utils/handler.js +7 -0
  91. package/dist/runtime/server/utils/impersonation.d.ts +48 -0
  92. package/dist/runtime/server/utils/impersonation.js +259 -0
  93. package/dist/runtime/server/utils/jwt.d.ts +24 -0
  94. package/dist/runtime/server/utils/jwt.js +77 -0
  95. package/dist/runtime/server/utils/logger.d.ts +18 -0
  96. package/dist/runtime/server/utils/logger.js +49 -0
  97. package/dist/runtime/server/utils/magicCodeStore.d.ts +27 -0
  98. package/dist/runtime/server/utils/magicCodeStore.js +66 -0
  99. package/dist/runtime/server/utils/mockCodeStore.d.ts +89 -0
  100. package/dist/runtime/server/utils/mockCodeStore.js +71 -0
  101. package/dist/runtime/server/utils/password.d.ts +33 -0
  102. package/dist/runtime/server/utils/password.js +48 -0
  103. package/dist/runtime/server/utils/refreshToken.d.ts +74 -0
  104. package/dist/runtime/server/utils/refreshToken.js +108 -0
  105. package/dist/runtime/server/utils/resetSessionStore.d.ts +12 -0
  106. package/dist/runtime/server/utils/resetSessionStore.js +29 -0
  107. package/dist/runtime/tasks/cleanup/magic-codes.d.ts +10 -0
  108. package/dist/runtime/tasks/cleanup/magic-codes.js +79 -0
  109. package/dist/runtime/tasks/cleanup/refresh-tokens.d.ts +10 -0
  110. package/dist/runtime/tasks/cleanup/refresh-tokens.js +55 -0
  111. package/dist/runtime/tasks/cleanup/reset-sessions.d.ts +8 -0
  112. package/dist/runtime/tasks/cleanup/reset-sessions.js +45 -0
  113. package/dist/runtime/types/augmentation.d.ts +73 -0
  114. package/dist/runtime/types/augmentation.js +0 -0
  115. package/dist/runtime/types/authCode.d.ts +60 -0
  116. package/dist/runtime/types/authCode.js +0 -0
  117. package/dist/runtime/types/callbacks.d.ts +54 -0
  118. package/dist/runtime/types/callbacks.js +0 -0
  119. package/dist/runtime/types/config.d.ts +129 -0
  120. package/dist/runtime/types/config.js +0 -0
  121. package/dist/runtime/types/hooks.d.ts +118 -0
  122. package/dist/runtime/types/hooks.js +0 -0
  123. package/dist/runtime/types/index.d.ts +13 -0
  124. package/dist/runtime/types/index.js +1 -0
  125. package/dist/runtime/types/providers.d.ts +212 -0
  126. package/dist/runtime/types/providers.js +0 -0
  127. package/dist/runtime/types/refresh.d.ts +61 -0
  128. package/dist/runtime/types/refresh.js +0 -0
  129. package/dist/runtime/types/routes.d.ts +30 -0
  130. package/dist/runtime/types/routes.js +0 -0
  131. package/dist/runtime/types/token.d.ts +182 -0
  132. package/dist/runtime/types/token.js +0 -0
  133. package/dist/types.d.mts +7 -0
  134. 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,4 @@
1
+ export function filterTimeSensitiveClaims(user) {
2
+ const { iat, exp, iss, ...stableUser } = user;
3
+ return stableUser;
4
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Authentication middleware for Nuxt Aegis
3
+ * Validates JWT tokens and protects routes according to Nitro route rules
4
+ */
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
6
+ export default _default;
@@ -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
+ }