@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,50 @@
1
+ import { defineEventHandler, getQuery, createError, sendRedirect } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { createLogger } from "../../utils/logger.js";
4
+ import { validateAndIncrementAttempts, retrieveAndDeleteMagicCode } from "../../utils/magicCodeStore.js";
5
+ import { createResetSession } from "../../utils/resetSessionStore.js";
6
+ const logger = createLogger("PasswordResetVerify");
7
+ export default defineEventHandler(async (event) => {
8
+ const config = useRuntimeConfig();
9
+ const passwordConfig = config.nuxtAegis?.providers?.password;
10
+ if (!passwordConfig) {
11
+ throw createError({
12
+ statusCode: 404,
13
+ message: "Password provider is not configured"
14
+ });
15
+ }
16
+ const query = getQuery(event);
17
+ const code = query.code;
18
+ if (!code) {
19
+ throw createError({
20
+ statusCode: 400,
21
+ message: "Verification code is required"
22
+ });
23
+ }
24
+ const magicCodeData = await validateAndIncrementAttempts(code);
25
+ if (!magicCodeData) {
26
+ throw createError({
27
+ statusCode: 400,
28
+ message: "Invalid or expired code"
29
+ });
30
+ }
31
+ if (magicCodeData.type !== "reset") {
32
+ throw createError({
33
+ statusCode: 400,
34
+ message: "Invalid code type"
35
+ });
36
+ }
37
+ const { email } = magicCodeData;
38
+ try {
39
+ const sessionId = await createResetSession(email);
40
+ await retrieveAndDeleteMagicCode(code);
41
+ const redirectUrl = `/reset-password?session=${sessionId}`;
42
+ return await sendRedirect(event, redirectUrl, 302);
43
+ } catch (error) {
44
+ logger.error("Reset verification failed", error);
45
+ throw createError({
46
+ statusCode: 500,
47
+ message: "Verification failed"
48
+ });
49
+ }
50
+ });
@@ -0,0 +1,8 @@
1
+ import type { RefreshResponse } from '../../types/index.js';
2
+ /**
3
+ * POST /auth/refresh
4
+ * Refresh endpoint to obtain new access tokens
5
+ * Rejects refresh requests for impersonated sessions
6
+ */
7
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<RefreshResponse>>;
8
+ export default _default;
@@ -0,0 +1,102 @@
1
+ import { defineEventHandler, getCookie, createError } from "h3";
2
+ import { generateToken, verifyToken } from "../utils/jwt.js";
3
+ import {
4
+ generateAndStoreRefreshToken,
5
+ hashRefreshToken,
6
+ getRefreshTokenData,
7
+ revokeRefreshToken
8
+ } from "../utils/refreshToken.js";
9
+ import { setRefreshTokenCookie } from "../utils/cookies.js";
10
+ import { processCustomClaims } from "../utils/customClaims.js";
11
+ import { useRuntimeConfig } from "#imports";
12
+ export default defineEventHandler(async (event) => {
13
+ const config = useRuntimeConfig(event);
14
+ const cookieConfig = config.nuxtAegis?.tokenRefresh?.cookie;
15
+ const tokenConfig = config.nuxtAegis?.token;
16
+ const tokenRefreshConfig = config.nuxtAegis?.tokenRefresh;
17
+ if (!tokenConfig || !tokenConfig.secret) {
18
+ throw createError({
19
+ statusCode: 500,
20
+ statusMessage: "Internal Server Error",
21
+ message: "Token configuration is missing"
22
+ });
23
+ }
24
+ const cookieName = cookieConfig?.cookieName || "nuxt-aegis-refresh";
25
+ const refreshToken = getCookie(event, cookieName);
26
+ if (!refreshToken) {
27
+ throw createError({
28
+ statusCode: 401,
29
+ statusMessage: "Unauthorized",
30
+ message: "No refresh token found"
31
+ });
32
+ }
33
+ try {
34
+ const authHeader = event.node.req.headers.authorization;
35
+ if (authHeader?.startsWith("Bearer ")) {
36
+ const token = authHeader.substring(7);
37
+ const currentToken = await verifyToken(token, tokenConfig.secret, false);
38
+ if (currentToken?.impersonation) {
39
+ throw createError({
40
+ statusCode: 403,
41
+ statusMessage: "Forbidden",
42
+ message: "Cannot refresh impersonated session. Please call unimpersonate to restore your original session."
43
+ });
44
+ }
45
+ }
46
+ const hashedRefreshToken = hashRefreshToken(refreshToken);
47
+ const storedRefreshToken = await getRefreshTokenData(hashedRefreshToken, event);
48
+ const isRevoked = storedRefreshToken?.isRevoked || false;
49
+ const isExpired = storedRefreshToken?.expiresAt ? Date.now() > storedRefreshToken.expiresAt : true;
50
+ if (!storedRefreshToken || isRevoked || isExpired) {
51
+ throw createError({
52
+ statusCode: 401,
53
+ statusMessage: "Unauthorized",
54
+ message: "Invalid or expired refresh token"
55
+ });
56
+ }
57
+ const providerUserInfo = storedRefreshToken.providerUserInfo;
58
+ const provider = storedRefreshToken.provider;
59
+ let customClaims = {};
60
+ const providerConfig = config.nuxtAegis?.providers?.[provider];
61
+ if (providerConfig && "customClaims" in providerConfig) {
62
+ const customClaimsConfig = providerConfig.customClaims;
63
+ if (customClaimsConfig) {
64
+ customClaims = await processCustomClaims(providerUserInfo, customClaimsConfig);
65
+ }
66
+ }
67
+ const payload = {
68
+ sub: String(providerUserInfo.sub || providerUserInfo.email || providerUserInfo.id || ""),
69
+ email: providerUserInfo.email,
70
+ name: providerUserInfo.name,
71
+ picture: providerUserInfo.picture,
72
+ provider
73
+ // Include provider name in JWT payload
74
+ };
75
+ const newToken = await generateToken(payload, tokenConfig, customClaims);
76
+ const newRefreshToken = await generateAndStoreRefreshToken(
77
+ providerUserInfo,
78
+ // RS-2: Store complete OAuth provider user data
79
+ provider,
80
+ // Store provider name
81
+ tokenRefreshConfig,
82
+ hashedRefreshToken,
83
+ // Pass previous token hash for rotation tracking
84
+ event
85
+ );
86
+ if (newRefreshToken) {
87
+ setRefreshTokenCookie(event, newRefreshToken, cookieConfig);
88
+ }
89
+ await revokeRefreshToken(hashedRefreshToken, event);
90
+ return {
91
+ success: true,
92
+ message: "Token refreshed successfully",
93
+ accessToken: newToken
94
+ };
95
+ } catch {
96
+ throw createError({
97
+ statusCode: 401,
98
+ statusMessage: "Unauthorized",
99
+ message: "Token refresh failed"
100
+ });
101
+ }
102
+ });
@@ -0,0 +1,28 @@
1
+ import type { TokenExchangeResponse } from '../../types/index.js';
2
+ /**
3
+ * POST /auth/token
4
+ * Token Exchange Endpoint - Authorization CODE to JWT Tokens
5
+ *
6
+ * This endpoint implements the second step of the OAuth 2.0 authorization code flow.
7
+ * It exchanges a short-lived authorization CODE for application-specific JWT tokens.
8
+ *
9
+ * Flow:
10
+ * 1. Accept authorization CODE from request body
11
+ * 2. Validate CODE exists in server-side key-value store
12
+ * 3. Retrieve and delete CODE atomically (single-use enforcement)
13
+ * 4. Generate JWT access token and refresh token
14
+ * 5. Return access token in JSON response body
15
+ * 6. Set refresh token as HttpOnly, Secure cookie
16
+ *
17
+ * Security:
18
+ * - Single-use CODE enforcement via immediate deletion
19
+ * - Generic 401 error for invalid/expired/reused CODEs
20
+ * - No specific failure reasons revealed to prevent information leakage
21
+ *
22
+ * @returns TokenExchangeResponse with access token and metadata
23
+ * @throws 400 Bad Request if CODE is missing from request body
24
+ * @throws 401 Unauthorized if CODE is invalid, expired, or already used (EH-4: Generic error)
25
+ * @throws 500 Internal Server Error if token generation fails
26
+ */
27
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<TokenExchangeResponse>>;
28
+ export default _default;
@@ -0,0 +1,90 @@
1
+ import { defineEventHandler, readBody, createError } from "h3";
2
+ import { retrieveAndDeleteAuthCode } from "../utils/authCodeStore.js";
3
+ import { useRuntimeConfig } from "#imports";
4
+ import { generateAuthTokens } from "../utils/auth.js";
5
+ import { setRefreshTokenCookie } from "../utils/cookies.js";
6
+ import { createLogger } from "../utils/logger.js";
7
+ const logger = createLogger("TokenExchange");
8
+ export default defineEventHandler(async (event) => {
9
+ const body = await readBody(event);
10
+ if (!body?.code) {
11
+ logger.security("Token exchange attempted without authorization code", {
12
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
13
+ event: "TOKEN_EXCHANGE_MISSING_CODE",
14
+ severity: "warning"
15
+ });
16
+ throw createError({
17
+ statusCode: 400,
18
+ statusMessage: "Bad Request",
19
+ message: "Missing authorization code"
20
+ });
21
+ }
22
+ const authCodeData = await retrieveAndDeleteAuthCode(body.code);
23
+ if (!authCodeData) {
24
+ logger.security("Token exchange failed - invalid authorization code", {
25
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
26
+ event: "TOKEN_EXCHANGE_INVALID_CODE",
27
+ codePrefix: `${body.code.substring(0, 8)}...`,
28
+ severity: "warning"
29
+ });
30
+ throw createError({
31
+ statusCode: 401,
32
+ statusMessage: "Unauthorized",
33
+ message: "Invalid or expired authorization code"
34
+ });
35
+ }
36
+ try {
37
+ const { accessToken, refreshToken } = await generateAuthTokens(
38
+ event,
39
+ authCodeData.providerUserInfo,
40
+ authCodeData.provider,
41
+ authCodeData.customClaims
42
+ );
43
+ if (refreshToken) {
44
+ const cookieConfig = useRuntimeConfig(event).nuxtAegis?.tokenRefresh?.cookie;
45
+ setRefreshTokenCookie(event, refreshToken, cookieConfig);
46
+ }
47
+ const tokenConfig = useRuntimeConfig(event).nuxtAegis?.token;
48
+ const expiresIn = tokenConfig?.expiresIn;
49
+ let expiresInSeconds;
50
+ if (expiresIn) {
51
+ if (typeof expiresIn === "number") {
52
+ expiresInSeconds = expiresIn;
53
+ } else if (typeof expiresIn === "string") {
54
+ const match = expiresIn.match(/^(\d+)([smhd])$/);
55
+ if (match && match[1] && match[2]) {
56
+ const value = Number.parseInt(match[1], 10);
57
+ const unit = match[2];
58
+ const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
59
+ const multiplier = multipliers[unit];
60
+ if (multiplier !== void 0) {
61
+ expiresInSeconds = value * multiplier;
62
+ }
63
+ }
64
+ }
65
+ }
66
+ const response = {
67
+ accessToken,
68
+ tokenType: "Bearer",
69
+ expiresIn: expiresInSeconds
70
+ };
71
+ logger.security("JWT tokens generated successfully", {
72
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
73
+ event: "TOKEN_GENERATION_SUCCESS",
74
+ expiresIn: expiresInSeconds
75
+ });
76
+ return response;
77
+ } catch (error) {
78
+ logger.error("Token generation error", {
79
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
80
+ event: "TOKEN_GENERATION_ERROR",
81
+ error: import.meta.dev ? error : "Error details hidden in production",
82
+ severity: "error"
83
+ });
84
+ throw createError({
85
+ statusCode: 500,
86
+ statusMessage: "Internal Server Error",
87
+ message: "Failed to generate authentication tokens"
88
+ });
89
+ }
90
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * POST /auth/unimpersonate
3
+ * Ends impersonation and restores the original user session
4
+ *
5
+ * Response:
6
+ * - accessToken: string - JWT for restored original session
7
+ * - Sets refresh token cookie for normal session
8
+ *
9
+ * Requirements:
10
+ * - Impersonation feature must be enabled
11
+ * - Current session must be impersonated
12
+ */
13
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
14
+ accessToken: string;
15
+ }>>;
16
+ export default _default;
@@ -0,0 +1,65 @@
1
+ import { defineEventHandler, createError, getHeader, setCookie } from "h3";
2
+ import { verifyToken } from "../utils/jwt.js";
3
+ import { endImpersonation } from "../utils/impersonation.js";
4
+ import { useRuntimeConfig } from "#imports";
5
+ import { createLogger } from "../utils/logger.js";
6
+ const logger = createLogger("Unimpersonate");
7
+ export default defineEventHandler(async (event) => {
8
+ const config = useRuntimeConfig();
9
+ if (!config.nuxtAegis?.impersonation?.enabled) {
10
+ throw createError({
11
+ statusCode: 404,
12
+ message: "Impersonation feature is not enabled"
13
+ });
14
+ }
15
+ try {
16
+ const authHeader = getHeader(event, "authorization");
17
+ if (!authHeader?.startsWith("Bearer ")) {
18
+ throw createError({
19
+ statusCode: 401,
20
+ message: "Authentication required"
21
+ });
22
+ }
23
+ const token = authHeader.substring(7);
24
+ const tokenConfig = config.nuxtAegis?.token;
25
+ if (!tokenConfig?.secret) {
26
+ throw createError({
27
+ statusCode: 500,
28
+ message: "Token configuration is missing"
29
+ });
30
+ }
31
+ const currentToken = await verifyToken(token, tokenConfig.secret);
32
+ if (!currentToken) {
33
+ throw createError({
34
+ statusCode: 401,
35
+ message: "Invalid or expired token"
36
+ });
37
+ }
38
+ const { accessToken, refreshTokenId } = await endImpersonation(currentToken, event);
39
+ const cookieConfig = config.nuxtAegis?.tokenRefresh?.cookie;
40
+ const cookieName = cookieConfig?.cookieName || "nuxt-aegis-refresh";
41
+ setCookie(event, cookieName, refreshTokenId, {
42
+ httpOnly: true,
43
+ secure: cookieConfig?.secure ?? true,
44
+ sameSite: cookieConfig?.sameSite || "lax",
45
+ path: cookieConfig?.path || "/",
46
+ maxAge: cookieConfig?.maxAge,
47
+ domain: cookieConfig?.domain
48
+ });
49
+ logger.security("Impersonation ended", {
50
+ originalUser: currentToken.impersonation?.originalUserId,
51
+ wasImpersonating: currentToken.sub
52
+ });
53
+ return { accessToken };
54
+ } catch (error) {
55
+ logger.error("Unimpersonate failed:", error);
56
+ const err = error;
57
+ if (err.statusCode) {
58
+ throw error;
59
+ }
60
+ throw createError({
61
+ statusCode: 500,
62
+ message: err.message || "Failed to end impersonation"
63
+ });
64
+ }
65
+ });
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../../.nuxt/tsconfig.server.json",
3
+ }
@@ -0,0 +1,94 @@
1
+ import type { H3Event } from 'h3';
2
+ import type { TokenPayload } from '../../types/index.js';
3
+ /**
4
+ * Generate authentication tokens from user data with optional custom claims
5
+ * This is the recommended way to generate tokens after successful OAuth authentication
6
+ *
7
+ * @param event - H3Event object
8
+ * @param providerUserInfo - Complete user object from the OAuth provider (will be stored with refresh token)
9
+ * @param provider - Provider name (e.g., 'google', 'github', 'microsoft', 'auth0')
10
+ * @param customClaims - Optional custom claims to add to the JWT (already processed)
11
+ * @returns Object containing accessToken and refreshToken
12
+ *
13
+ * @example
14
+ * ```typescript
15
+ * const tokens = await generateAuthTokens(event, providerUserInfo, 'google', {
16
+ * role: 'admin',
17
+ * permissions: ['read', 'write'],
18
+ * })
19
+ * ```
20
+ */
21
+ export declare function generateAuthTokens(event: H3Event, providerUserInfo: Record<string, unknown>, provider: string, customClaims?: Record<string, unknown>): Promise<{
22
+ accessToken: string;
23
+ refreshToken?: string;
24
+ }>;
25
+ /**
26
+ * Ensures the event has an authenticated user and narrows the type
27
+ * Returns the event with narrowed context type for better type inference
28
+ *
29
+ * @param event - H3Event object
30
+ * @returns Event with guaranteed user context
31
+ * @throws 401 Unauthorized if user is not authenticated
32
+ *
33
+ * @example
34
+ * ```typescript
35
+ * export default defineEventHandler((event) => {
36
+ * const authedEvent = requireAuth(event)
37
+ * // TypeScript knows authedEvent.context.user is defined
38
+ * return { userId: authedEvent.context.user.sub }
39
+ * })
40
+ * ```
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * // With custom claims typing
45
+ * interface MyTokenPayload extends TokenPayload {
46
+ * role: string
47
+ * permissions: string[]
48
+ * }
49
+ *
50
+ * export default defineEventHandler((event) => {
51
+ * const authedEvent = requireAuth<MyTokenPayload>(event)
52
+ * // TypeScript knows about role and permissions
53
+ * return { role: authedEvent.context.user.role }
54
+ * })
55
+ * ```
56
+ */
57
+ export declare function requireAuth<T extends TokenPayload = TokenPayload>(event: H3Event): H3Event & {
58
+ context: {
59
+ user: T;
60
+ };
61
+ };
62
+ /**
63
+ * Get the authenticated user from the event context
64
+ * This is a convenience function that combines requireAuth and context extraction
65
+ *
66
+ * @param event - H3Event object
67
+ * @returns The authenticated user payload
68
+ * @throws 401 Unauthorized if user is not authenticated
69
+ *
70
+ * @example
71
+ * ```typescript
72
+ * export default defineEventHandler((event) => {
73
+ * const user = getAuthUser(event)
74
+ * // TypeScript knows user is defined
75
+ * return { userId: user.sub, email: user.email }
76
+ * })
77
+ * ```
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * // With custom claims typing
82
+ * interface MyTokenPayload extends TokenPayload {
83
+ * role: string
84
+ * permissions: string[]
85
+ * }
86
+ *
87
+ * export default defineEventHandler((event) => {
88
+ * const user = getAuthUser<MyTokenPayload>(event)
89
+ * // TypeScript knows about role and permissions
90
+ * return { role: user.role, permissions: user.permissions }
91
+ * })
92
+ * ```
93
+ */
94
+ export declare function getAuthUser<T extends TokenPayload = TokenPayload>(event: H3Event): T;
@@ -0,0 +1,54 @@
1
+ import { useRuntimeConfig } from "#imports";
2
+ import { createError } from "h3";
3
+ import { generateAndStoreRefreshToken } from "./refreshToken.js";
4
+ import { generateToken } from "./jwt.js";
5
+ export async function generateAuthTokens(event, providerUserInfo, provider, customClaims) {
6
+ const config = useRuntimeConfig(event);
7
+ const tokenConfig = config.nuxtAegis?.token;
8
+ const tokenRefreshConfig = config.nuxtAegis?.tokenRefresh;
9
+ if (!tokenConfig || !tokenConfig.secret) {
10
+ throw new Error("Token configuration is missing. Please configure nuxtAegis.token in your nuxt.config.ts");
11
+ }
12
+ const payload = {
13
+ sub: String(providerUserInfo.sub || providerUserInfo.email || providerUserInfo.id || ""),
14
+ email: providerUserInfo.email,
15
+ name: providerUserInfo.name,
16
+ picture: providerUserInfo.picture,
17
+ provider
18
+ // Include provider name in JWT payload
19
+ };
20
+ const refreshToken = await generateAndStoreRefreshToken(
21
+ providerUserInfo,
22
+ // RS-2: Store complete user object
23
+ provider,
24
+ // Store provider name for custom claims refresh
25
+ tokenRefreshConfig,
26
+ void 0,
27
+ // No previous token hash for initial auth
28
+ event
29
+ );
30
+ return {
31
+ accessToken: await generateToken(payload, tokenConfig, customClaims),
32
+ refreshToken
33
+ };
34
+ }
35
+ export function requireAuth(event) {
36
+ if (!event.context.user) {
37
+ throw createError({
38
+ statusCode: 401,
39
+ statusMessage: "Unauthorized",
40
+ message: "Authentication required"
41
+ });
42
+ }
43
+ return event;
44
+ }
45
+ export function getAuthUser(event) {
46
+ if (!event.context.user) {
47
+ throw createError({
48
+ statusCode: 401,
49
+ statusMessage: "Unauthorized",
50
+ message: "Authentication required"
51
+ });
52
+ }
53
+ return event.context.user;
54
+ }
@@ -0,0 +1,137 @@
1
+ import type { H3Event } from 'h3';
2
+ import type { AuthCodeData } from '../../types/index.js';
3
+ /**
4
+ * Authorization Code Store Utilities
5
+ *
6
+ * Provides server-side storage and management for short-lived authorization CODEs
7
+ * used in the OAuth 2.0 authorization code flow.
8
+ *
9
+ * Flow:
10
+ * 1. Generate and store authorization CODE after OAuth authentication
11
+ * 2. Store CODE with user info and provider tokens in memory
12
+ * 3. Set 60-second expiration (configurable via CF-9)
13
+ * 4. Retrieve and delete CODE when exchanging for tokens
14
+ * 5. Ensure single-use by immediate deletion
15
+ * 6. Automatic cleanup of expired CODEs
16
+ *
17
+ * Security Features:
18
+ * - Cryptographically secure random CODE generation (crypto.randomBytes)
19
+ * - Single-use enforcement (delete immediately after retrieval)
20
+ * - Automatic cleanup of expired CODEs
21
+ * - Validation before allowing token exchange
22
+ */
23
+ /**
24
+ * Generate a cryptographically secure authorization code
25
+ *
26
+ * @returns A base64url-encoded random authorization code
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const code = generateAuthCode()
31
+ * // Returns: "X7k9mP2nQ5vL8wR4tY6uZ3bN1cM0dF5g"
32
+ * ```
33
+ */
34
+ export declare function generateAuthCode(): string;
35
+ /**
36
+ * Store authorization code with associated user and provider data
37
+ * Server-side in-memory key-value store for temporary authorization CODE storage
38
+ * Associate CODE with user information and provider tokens
39
+ * Set expiration time of 60 seconds
40
+ * Store CODE in server-side in-memory key-value store
41
+ *
42
+ * @param code - The authorization code to store
43
+ * @param providerUserInfo - Complete OAuth provider user data
44
+ * @param providerTokens - Tokens received from OAuth provider
45
+ * @param providerTokens.access_token - Provider access token
46
+ * @param providerTokens.refresh_token - Optional provider refresh token
47
+ * @param providerTokens.id_token - Optional provider ID token
48
+ * @param providerTokens.expires_in - Optional token expiration time
49
+ * @param provider - Provider name (e.g., 'google', 'github', 'microsoft', 'auth0')
50
+ * @param customClaims - Resolved custom claims (already processed from static or callback config)
51
+ * @param expiresIn - Expiration time in seconds (default: 60)
52
+ * @param _event - H3Event for Nitro storage access (optional, currently unused)
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * await storeAuthCode('X7k9mP...', providerUserInfo, providerTokens, 'google', customClaims)
57
+ * ```
58
+ */
59
+ export declare function storeAuthCode(code: string, providerUserInfo: Record<string, unknown>, providerTokens: {
60
+ access_token: string;
61
+ refresh_token?: string;
62
+ id_token?: string;
63
+ expires_in?: number;
64
+ }, provider: string, customClaims: Record<string, unknown> | undefined, expiresIn?: number, // CS-4: Default 60 seconds
65
+ _event?: H3Event): Promise<void>;
66
+ /**
67
+ * Validate that an authorization code exists and has not expired
68
+ *
69
+ * @param code - The authorization code to validate
70
+ * @returns The auth code data if valid, null if invalid or expired
71
+ *
72
+ * @example
73
+ * ```typescript
74
+ * const data = await validateAuthCode('X7k9mP...')
75
+ * if (!data) {
76
+ * throw new Error('Invalid or expired code')
77
+ * }
78
+ * ```
79
+ */
80
+ export declare function validateAuthCode(code: string): Promise<AuthCodeData | null>;
81
+ /**
82
+ * Retrieve and delete authorization code in a single atomic operation
83
+ * Immediately delete CODE after successful exchange for single-use enforcement
84
+ * Prevent CODE reuse by validating existence before deletion
85
+ * Ensure single-use only
86
+ * Delete CODE from store after validation
87
+ *
88
+ * @param code - The authorization code to retrieve and delete
89
+ * @returns The auth code data if valid, null if invalid, expired, or already used
90
+ *
91
+ * @example
92
+ * ```typescript
93
+ * const data = await retrieveAndDeleteAuthCode('X7k9mP...')
94
+ * if (!data) {
95
+ * // Code was invalid, expired, or already used
96
+ * throw new Error('Invalid authorization code')
97
+ * }
98
+ * // Code is valid and has been consumed (deleted)
99
+ * ```
100
+ */
101
+ export declare function retrieveAndDeleteAuthCode(code: string): Promise<AuthCodeData | null>;
102
+ /**
103
+ * Clean up expired authorization codes from storage
104
+ * Automatically remove expired CODEs
105
+ * Support automatic cleanup without blocking requests
106
+ * Automatic cleanup of expired CODEs
107
+ *
108
+ * This function should be called periodically to prevent memory buildup
109
+ * from expired codes that were never exchanged.
110
+ *
111
+ * @returns Number of codes cleaned up
112
+ *
113
+ * @example
114
+ * ```typescript
115
+ * // Run cleanup periodically
116
+ * const cleaned = await cleanupExpiredAuthCodes()
117
+ * console.log(`Cleaned up ${cleaned} expired codes`)
118
+ * ```
119
+ */
120
+ export declare function cleanupExpiredAuthCodes(): Promise<number>;
121
+ /**
122
+ * Get statistics about the authorization code store
123
+ * Useful for monitoring and debugging
124
+ *
125
+ * @returns Statistics object with total, expired, and valid code counts
126
+ *
127
+ * @example
128
+ * ```typescript
129
+ * const stats = await getAuthCodeStats()
130
+ * console.log(`Total: ${stats.total}, Valid: ${stats.valid}, Expired: ${stats.expired}`)
131
+ * ```
132
+ */
133
+ export declare function getAuthCodeStats(): Promise<{
134
+ total: number;
135
+ valid: number;
136
+ expired: number;
137
+ }>;