@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,77 @@
1
+ import { decodeJwt, jwtVerify, SignJWT } from "jose";
2
+ import { filterReservedClaims, validateClaimTypes } from "./customClaims.js";
3
+ import { createLogger } from "./logger.js";
4
+ const logger = createLogger("JWT");
5
+ function validateTokenSize(payload) {
6
+ if (process.env.NODE_ENV === "production") {
7
+ return;
8
+ }
9
+ const payloadString = JSON.stringify(payload);
10
+ const sizeInBytes = payloadString.length;
11
+ const thresholdBytes = 1024;
12
+ if (sizeInBytes > thresholdBytes) {
13
+ logger.warn(
14
+ `Token payload size (${sizeInBytes} bytes) exceeds recommended threshold (${thresholdBytes} bytes). Consider reducing the payload size by removing unnecessary claims or using references instead of large values. Large tokens can impact performance and may be rejected by some systems.`
15
+ );
16
+ }
17
+ }
18
+ export async function generateToken(payload, config, customClaims) {
19
+ if (!config.secret) {
20
+ throw new Error("Token secret is required");
21
+ }
22
+ const secret = new TextEncoder().encode(config.secret);
23
+ const expiresIn = config.expiresIn || "1h";
24
+ let safeClaims = {};
25
+ if (customClaims) {
26
+ const filtered = filterReservedClaims(customClaims);
27
+ safeClaims = validateClaimTypes(filtered);
28
+ }
29
+ const finalPayload = { ...payload, ...safeClaims };
30
+ validateTokenSize(finalPayload);
31
+ let jwt = new SignJWT(finalPayload).setProtectedHeader({ alg: config.algorithm || "HS256" }).setIssuedAt().setSubject(payload.sub);
32
+ if (config.issuer) {
33
+ jwt = jwt.setIssuer(config.issuer);
34
+ }
35
+ if (config.audience) {
36
+ jwt = jwt.setAudience(config.audience);
37
+ }
38
+ if (typeof expiresIn === "number") {
39
+ jwt = jwt.setExpirationTime(Math.floor(Date.now() / 1e3) + expiresIn);
40
+ } else {
41
+ jwt = jwt.setExpirationTime(expiresIn);
42
+ }
43
+ return await jwt.sign(secret);
44
+ }
45
+ export async function updateTokenWithClaims(token, claims, config) {
46
+ if (!config.secret) {
47
+ throw new Error("Token secret is required");
48
+ }
49
+ const secret = new TextEncoder().encode(config.secret);
50
+ const { payload } = await jwtVerify(token, secret);
51
+ const updatedPayload = {
52
+ ...payload,
53
+ ...claims,
54
+ sub: payload.sub
55
+ };
56
+ return await generateToken(updatedPayload, config);
57
+ }
58
+ export async function verifyToken(token, secret, checkExpiration = true) {
59
+ if (!token || !secret) {
60
+ return null;
61
+ }
62
+ try {
63
+ const secretKey = new TextEncoder().encode(secret);
64
+ if (checkExpiration) {
65
+ const { payload } = await jwtVerify(token, secretKey);
66
+ return payload;
67
+ } else {
68
+ const payload = decodeJwt(token);
69
+ const beforeExpiry = new Date((payload.exp || Date.now()) - 1e3);
70
+ await jwtVerify(token, secretKey, { currentDate: beforeExpiry });
71
+ return payload;
72
+ }
73
+ } catch {
74
+ logger.error("Token verification failed");
75
+ return null;
76
+ }
77
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Logger instance for consistent logging across server-side code
3
+ */
4
+ export interface Logger {
5
+ debug: (message: string, ...args: unknown[]) => void;
6
+ info: (message: string, ...args: unknown[]) => void;
7
+ warn: (message: string, ...args: unknown[]) => void;
8
+ error: (message: string, ...args: unknown[]) => void;
9
+ security: (message: string, ...args: unknown[]) => void;
10
+ }
11
+ /**
12
+ * Create a logger instance with a specific context
13
+ * Respects the logging configuration from runtime config
14
+ *
15
+ * @param context - Context identifier for log messages (e.g., 'Auth', 'JWT', 'OAuth')
16
+ * @returns Logger instance with debug, info, warn, error, and security methods
17
+ */
18
+ export declare function createLogger(context: string): Logger;
@@ -0,0 +1,49 @@
1
+ import { consola } from "consola";
2
+ import { useRuntimeConfig } from "#imports";
3
+ export function createLogger(context) {
4
+ const prefix = `[Nuxt Aegis][${context}]`;
5
+ const config = useRuntimeConfig();
6
+ const loggingConfig = config.nuxtAegis?.logging;
7
+ const level = loggingConfig?.level || "info";
8
+ if (level === "debug") {
9
+ consola.level = 4;
10
+ }
11
+ return {
12
+ debug: (message, ...args) => {
13
+ consola.debug(prefix, message, ...args);
14
+ },
15
+ info: (message, ...args) => {
16
+ const config2 = useRuntimeConfig();
17
+ const loggingConfig2 = config2.nuxtAegis?.logging;
18
+ const level2 = loggingConfig2?.level || "info";
19
+ if (level2 !== "silent" && level2 !== "error" && level2 !== "warn") {
20
+ consola.info(prefix, message, ...args);
21
+ }
22
+ },
23
+ warn: (message, ...args) => {
24
+ const config2 = useRuntimeConfig();
25
+ const loggingConfig2 = config2.nuxtAegis?.logging;
26
+ const level2 = loggingConfig2?.level || "info";
27
+ if (level2 !== "silent" && level2 !== "error") {
28
+ consola.warn(prefix, message, ...args);
29
+ }
30
+ },
31
+ error: (message, ...args) => {
32
+ const config2 = useRuntimeConfig();
33
+ const loggingConfig2 = config2.nuxtAegis?.logging;
34
+ const level2 = loggingConfig2?.level || "info";
35
+ if (level2 !== "silent") {
36
+ consola.error(prefix, message, ...args);
37
+ }
38
+ },
39
+ security: (message, ...args) => {
40
+ const config2 = useRuntimeConfig();
41
+ const loggingConfig2 = config2.nuxtAegis?.logging;
42
+ const level2 = loggingConfig2?.level || "info";
43
+ const securityEnabled = loggingConfig2?.security === true || level2 === "debug";
44
+ if (securityEnabled && level2 !== "silent") {
45
+ consola.warn(`[Nuxt Aegis Security][${context}]`, message, ...args);
46
+ }
47
+ }
48
+ };
49
+ }
@@ -0,0 +1,27 @@
1
+ export interface MagicCodeData {
2
+ email: string;
3
+ type: 'register' | 'login' | 'reset';
4
+ hashedPassword?: string;
5
+ attempts: number;
6
+ maxAttempts: number;
7
+ createdAt: number;
8
+ expiresAt: number;
9
+ [key: string]: unknown;
10
+ }
11
+ /**
12
+ * Generate a 6-digit magic code
13
+ */
14
+ export declare function generateMagicCode(): string;
15
+ /**
16
+ * Store a magic code
17
+ */
18
+ export declare function storeMagicCode(email: string, type: 'register' | 'login' | 'reset', data: Omit<MagicCodeData, 'attempts' | 'createdAt' | 'expiresAt' | 'email' | 'type'>, ttl?: number, // 10 minutes
19
+ maxAttempts?: number): Promise<string>;
20
+ /**
21
+ * Validate and increment attempts for a magic code
22
+ */
23
+ export declare function validateAndIncrementAttempts(code: string): Promise<MagicCodeData | null>;
24
+ /**
25
+ * Retrieve and delete a magic code
26
+ */
27
+ export declare function retrieveAndDeleteMagicCode(code: string): Promise<MagicCodeData | null>;
@@ -0,0 +1,66 @@
1
+ import { randomInt } from "node:crypto";
2
+ import { useStorage } from "#imports";
3
+ import { createLogger } from "./logger.js";
4
+ const logger = createLogger("MagicCode");
5
+ export function generateMagicCode() {
6
+ return randomInt(1e5, 999999).toString();
7
+ }
8
+ export async function storeMagicCode(email, type, data, ttl = 600, maxAttempts = 5) {
9
+ const storage = useStorage();
10
+ const normalizedEmail = email.toLowerCase();
11
+ const code = generateMagicCode();
12
+ const now = Date.now();
13
+ const lookupKey = `magic-lookup:${normalizedEmail}:${type}`;
14
+ const existingCode = await storage.getItem(lookupKey);
15
+ if (existingCode) {
16
+ await storage.removeItem(`magic:${existingCode}`);
17
+ }
18
+ const magicCodeData = {
19
+ ...data,
20
+ email: normalizedEmail,
21
+ type,
22
+ attempts: 0,
23
+ maxAttempts,
24
+ createdAt: now,
25
+ expiresAt: now + ttl * 1e3
26
+ };
27
+ await storage.setItem(`magic:${code}`, magicCodeData);
28
+ await storage.setItem(lookupKey, code);
29
+ logger.debug(`Magic code stored for ${normalizedEmail} (${type})`);
30
+ return code;
31
+ }
32
+ export async function validateAndIncrementAttempts(code) {
33
+ const storage = useStorage();
34
+ const key = `magic:${code}`;
35
+ const data = await storage.getItem(key);
36
+ if (!data) {
37
+ return null;
38
+ }
39
+ if (Date.now() > data.expiresAt) {
40
+ await storage.removeItem(key);
41
+ const lookupKey = `magic-lookup:${data.email}:${data.type}`;
42
+ await storage.removeItem(lookupKey);
43
+ return null;
44
+ }
45
+ if (data.attempts >= data.maxAttempts) {
46
+ await storage.removeItem(key);
47
+ const lookupKey = `magic-lookup:${data.email}:${data.type}`;
48
+ await storage.removeItem(lookupKey);
49
+ return null;
50
+ }
51
+ data.attempts++;
52
+ await storage.setItem(key, data);
53
+ return data;
54
+ }
55
+ export async function retrieveAndDeleteMagicCode(code) {
56
+ const storage = useStorage();
57
+ const key = `magic:${code}`;
58
+ const data = await storage.getItem(key);
59
+ if (!data) {
60
+ return null;
61
+ }
62
+ await storage.removeItem(key);
63
+ const lookupKey = `magic-lookup:${data.email}:${data.type}`;
64
+ await storage.removeItem(lookupKey);
65
+ return data;
66
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Mock Authorization Code Storage
3
+ *
4
+ * Simulates OAuth provider's authorization code storage and validation.
5
+ * This is NOT the Aegis authorization CODE - this simulates the provider's code.
6
+ *
7
+ * Stores temporary authorization codes that are:
8
+ * - Single-use (deleted after exchange)
9
+ * - Short-lived (expire after 60 seconds)
10
+ * - Associated with selected user persona
11
+ *
12
+ * DEVELOPMENT/TEST ONLY - Not available in production
13
+ */
14
+ interface MockAuthCode {
15
+ code: string;
16
+ /** Selected user persona identifier from mockUsers config */
17
+ userId: string;
18
+ clientId: string;
19
+ redirectUri: string;
20
+ createdAt: number;
21
+ expiresAt: number;
22
+ }
23
+ /**
24
+ * Generate a cryptographically random mock authorization code
25
+ */
26
+ export declare function generateMockCode(): string;
27
+ /**
28
+ * Store a mock authorization code with expiration
29
+ *
30
+ * @param data - Code data object
31
+ * @param data.code - Authorization code string
32
+ * @param data.userId - User persona identifier
33
+ * @param data.clientId - OAuth client ID
34
+ * @param data.redirectUri - OAuth redirect URI
35
+ */
36
+ export declare function storeMockCode(data: {
37
+ code: string;
38
+ userId: string;
39
+ clientId: string;
40
+ redirectUri: string;
41
+ }): void;
42
+ /**
43
+ * Retrieve and delete a mock authorization code (single-use enforcement)
44
+ *
45
+ * @param code - Authorization code to retrieve
46
+ * @returns Code data if valid and not expired, null otherwise
47
+ */
48
+ export declare function retrieveAndDeleteMockCode(code: string): MockAuthCode | null;
49
+ /**
50
+ * Cleanup expired mock codes (maintenance)
51
+ *
52
+ * @returns Number of codes cleaned up
53
+ */
54
+ export declare function cleanupExpiredMockCodes(): number;
55
+ /**
56
+ * Get count of active mock codes (for debugging)
57
+ */
58
+ export declare function getMockCodeCount(): number;
59
+ /**
60
+ * Clear all mock codes (for testing)
61
+ */
62
+ export declare function clearAllMockCodes(): void;
63
+ /**
64
+ * Store a mock access token with user mapping
65
+ * Allows userinfo endpoint to retrieve the correct user
66
+ *
67
+ * @param token - Access token string
68
+ * @param userId - User persona identifier
69
+ */
70
+ export declare function storeMockToken(token: string, userId: string): void;
71
+ /**
72
+ * Get user ID for a mock access token
73
+ *
74
+ * @param token - Access token string
75
+ * @returns User persona identifier or null if token not found
76
+ */
77
+ export declare function getUserForMockToken(token: string): string | null;
78
+ /**
79
+ * Clear expired mock tokens (maintenance)
80
+ * Tokens older than 2 hours are removed
81
+ *
82
+ * @returns Number of tokens cleaned up
83
+ */
84
+ export declare function cleanupExpiredMockTokens(): number;
85
+ /**
86
+ * Clear all mock tokens (for testing)
87
+ */
88
+ export declare function clearAllMockTokens(): void;
89
+ export {};
@@ -0,0 +1,71 @@
1
+ const mockCodes = /* @__PURE__ */ new Map();
2
+ const mockTokens = /* @__PURE__ */ new Map();
3
+ export function generateMockCode() {
4
+ const randomBytes = crypto.getRandomValues(new Uint8Array(16));
5
+ const randomHex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
6
+ return `mock_code_${randomHex}_${Date.now()}`;
7
+ }
8
+ export function storeMockCode(data) {
9
+ const now = Date.now();
10
+ mockCodes.set(data.code, {
11
+ ...data,
12
+ createdAt: now,
13
+ expiresAt: now + 6e4
14
+ // 60 seconds - standard OAuth code lifetime
15
+ });
16
+ }
17
+ export function retrieveAndDeleteMockCode(code) {
18
+ const codeData = mockCodes.get(code);
19
+ if (!codeData) {
20
+ return null;
21
+ }
22
+ if (Date.now() > codeData.expiresAt) {
23
+ mockCodes.delete(code);
24
+ return null;
25
+ }
26
+ mockCodes.delete(code);
27
+ return codeData;
28
+ }
29
+ export function cleanupExpiredMockCodes() {
30
+ const now = Date.now();
31
+ let cleaned = 0;
32
+ for (const [code, data] of mockCodes.entries()) {
33
+ if (now > data.expiresAt) {
34
+ mockCodes.delete(code);
35
+ cleaned++;
36
+ }
37
+ }
38
+ return cleaned;
39
+ }
40
+ export function getMockCodeCount() {
41
+ return mockCodes.size;
42
+ }
43
+ export function clearAllMockCodes() {
44
+ mockCodes.clear();
45
+ }
46
+ export function storeMockToken(token, userId) {
47
+ mockTokens.set(token, {
48
+ token,
49
+ userId,
50
+ createdAt: Date.now()
51
+ });
52
+ }
53
+ export function getUserForMockToken(token) {
54
+ const tokenData = mockTokens.get(token);
55
+ return tokenData?.userId || null;
56
+ }
57
+ export function cleanupExpiredMockTokens() {
58
+ const now = Date.now();
59
+ const maxAge = 2 * 60 * 60 * 1e3;
60
+ let cleaned = 0;
61
+ for (const [token, data] of mockTokens.entries()) {
62
+ if (now - data.createdAt > maxAge) {
63
+ mockTokens.delete(token);
64
+ cleaned++;
65
+ }
66
+ }
67
+ return cleaned;
68
+ }
69
+ export function clearAllMockTokens() {
70
+ mockTokens.clear();
71
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Normalize email address
3
+ */
4
+ export declare function normalizeEmail(email: string): string;
5
+ /**
6
+ * Validate email format
7
+ */
8
+ export declare function validateEmailFormat(email: string): {
9
+ valid: boolean;
10
+ error?: string;
11
+ };
12
+ /**
13
+ * Hash a password using scrypt
14
+ */
15
+ export declare function hashPassword(password: string): Promise<string>;
16
+ /**
17
+ * Verify a password against a hash
18
+ */
19
+ export declare function verifyPassword(password: string, hash: string): Promise<boolean>;
20
+ /**
21
+ * Validate password strength
22
+ */
23
+ export declare function validatePasswordStrength(password: string, policy?: {
24
+ minLength?: number;
25
+ requireUppercase?: boolean;
26
+ requireLowercase?: boolean;
27
+ requireNumber?: boolean;
28
+ requireSpecial?: boolean;
29
+ }): boolean | string[];
30
+ /**
31
+ * Obfuscate magic code for logging
32
+ */
33
+ export declare function obfuscateMagicCode(code: string): string;
@@ -0,0 +1,48 @@
1
+ import { scrypt, randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { promisify } from "node:util";
3
+ const scryptAsync = promisify(scrypt);
4
+ export function normalizeEmail(email) {
5
+ return email.trim().toLowerCase();
6
+ }
7
+ export function validateEmailFormat(email) {
8
+ const emailRegex = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;
9
+ if (!emailRegex.test(email)) {
10
+ return { valid: false, error: "Invalid email format" };
11
+ }
12
+ return { valid: true };
13
+ }
14
+ export async function hashPassword(password) {
15
+ const salt = randomBytes(16).toString("hex");
16
+ const derivedKey = await scryptAsync(password, salt, 64);
17
+ return `${salt}:${derivedKey.toString("hex")}`;
18
+ }
19
+ export async function verifyPassword(password, hash) {
20
+ const [salt, key] = hash.split(":");
21
+ if (!salt || !key) return false;
22
+ const keyBuffer = Buffer.from(key, "hex");
23
+ const derivedKey = await scryptAsync(password, salt, 64);
24
+ return timingSafeEqual(keyBuffer, derivedKey);
25
+ }
26
+ export function validatePasswordStrength(password, policy = {}) {
27
+ const errors = [];
28
+ const minLength = policy.minLength ?? 8;
29
+ if (password.length < minLength) {
30
+ errors.push(`Password must be at least ${minLength} characters long`);
31
+ }
32
+ if (policy.requireUppercase && !/[A-Z]/.test(password)) {
33
+ errors.push("Password must contain at least one uppercase letter");
34
+ }
35
+ if (policy.requireLowercase && !/[a-z]/.test(password)) {
36
+ errors.push("Password must contain at least one lowercase letter");
37
+ }
38
+ if (policy.requireNumber && !/\d/.test(password)) {
39
+ errors.push("Password must contain at least one number");
40
+ }
41
+ if (policy.requireSpecial && !/[!@#$%^&*(),.?":{}|<>]/.test(password)) {
42
+ errors.push("Password must contain at least one special character");
43
+ }
44
+ return errors.length === 0 ? true : errors;
45
+ }
46
+ export function obfuscateMagicCode(code) {
47
+ return code.slice(-2).padStart(code.length, "*");
48
+ }
@@ -0,0 +1,74 @@
1
+ import type { H3Event } from 'h3';
2
+ import type { RefreshTokenData, TokenRefreshConfig, EncryptionConfig } from '../../types/index.js';
3
+ /**
4
+ * Hash a refresh token using SHA-256
5
+ * @param token - The refresh token to hash
6
+ * @returns The hashed token as a hex string
7
+ */
8
+ export declare function hashRefreshToken(token: string): string;
9
+ /**
10
+ * Encrypt data using AES-256-GCM
11
+ * @param data - Data to encrypt
12
+ * @param key - Encryption key (must be 32 bytes for AES-256)
13
+ * @returns Encrypted data as base64 string with IV prepended
14
+ */
15
+ export declare function encryptData(data: unknown, key: string): string;
16
+ /**
17
+ * Decrypt data using AES-256-GCM
18
+ * @param encrypted - Encrypted data as base64 string
19
+ * @param key - Encryption key (must be 32 bytes for AES-256)
20
+ * @returns Decrypted data
21
+ */
22
+ export declare function decryptData(encrypted: string, key: string): unknown;
23
+ /**
24
+ * Get encryption config from runtime config
25
+ * @param event - H3 event (optional, for server context)
26
+ * @returns Encryption configuration
27
+ */
28
+ export declare function getEncryptionConfig(event?: H3Event): EncryptionConfig;
29
+ /**
30
+ * Store refresh token data in persistent storage
31
+ * Handles encryption transparently if enabled
32
+ * @param tokenHash - Hashed refresh token (used as storage key)
33
+ * @param data - Refresh token data to store
34
+ * @param event - H3 event for runtime config access
35
+ */
36
+ export declare function storeRefreshTokenData(tokenHash: string, data: RefreshTokenData, event?: H3Event): Promise<void>;
37
+ /**
38
+ * Retrieve refresh token data from persistent storage
39
+ * Handles decryption transparently if enabled
40
+ * @param tokenHash - Hashed refresh token (storage key)
41
+ * @param event - H3 event for runtime config access
42
+ * @returns Refresh token data or null if not found
43
+ */
44
+ export declare function getRefreshTokenData(tokenHash: string, event?: H3Event): Promise<RefreshTokenData | null>;
45
+ /**
46
+ * Delete refresh token data from storage
47
+ * @param tokenHash - Hashed refresh token (storage key)
48
+ */
49
+ export declare function deleteRefreshTokenData(tokenHash: string): Promise<void>;
50
+ /**
51
+ * Revoke a refresh token (mark as revoked without deleting)
52
+ * @param tokenHash - Hashed refresh token (storage key)
53
+ * @param event - H3 event for runtime config access
54
+ */
55
+ export declare function revokeRefreshToken(tokenHash: string, event?: H3Event): Promise<void>;
56
+ /**
57
+ * Generates a refresh token and stores it with user data
58
+ *
59
+ * @param providerUserInfo - Complete OAuth provider user data
60
+ * @param provider - Provider name (e.g., 'google', 'github', 'microsoft', 'auth0')
61
+ * @param config - Token refresh configuration
62
+ * @param previousTokenHash - Hash of previous refresh token for rotation tracking
63
+ * @param event - H3Event for Nitro storage access
64
+ * @returns The generated refresh token string
65
+ */
66
+ export declare function generateAndStoreRefreshToken(providerUserInfo: Record<string, unknown>, provider: string, config: TokenRefreshConfig, previousTokenHash?: string, event?: H3Event): Promise<string | undefined>;
67
+ /**
68
+ * Delete all refresh tokens for a specific user
69
+ * Used during password change or account deletion
70
+ * @param email - User email to match
71
+ * @param exceptTokenHash - Optional token hash to preserve (e.g. current session)
72
+ * @param event - H3 event for runtime config access
73
+ */
74
+ export declare function deleteUserRefreshTokens(email: string, exceptTokenHash?: string, event?: H3Event): Promise<void>;
@@ -0,0 +1,108 @@
1
+ import { createHash, createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+ import { useStorage, useRuntimeConfig } from "#imports";
3
+ export function hashRefreshToken(token) {
4
+ return createHash("sha256").update(token).digest("hex");
5
+ }
6
+ export function encryptData(data, key) {
7
+ const keyBuffer = Buffer.from(key.padEnd(32, "0").slice(0, 32));
8
+ const iv = randomBytes(12);
9
+ const cipher = createCipheriv("aes-256-gcm", keyBuffer, iv);
10
+ const jsonData = JSON.stringify(data);
11
+ let encrypted = cipher.update(jsonData, "utf8", "base64");
12
+ encrypted += cipher.final("base64");
13
+ const authTag = cipher.getAuthTag();
14
+ const combined = Buffer.concat([
15
+ iv,
16
+ authTag,
17
+ Buffer.from(encrypted, "base64")
18
+ ]);
19
+ return combined.toString("base64");
20
+ }
21
+ export function decryptData(encrypted, key) {
22
+ try {
23
+ const keyBuffer = Buffer.from(key.padEnd(32, "0").slice(0, 32));
24
+ const combined = Buffer.from(encrypted, "base64");
25
+ const iv = combined.subarray(0, 12);
26
+ const authTag = combined.subarray(12, 28);
27
+ const encryptedData = combined.subarray(28);
28
+ const decipher = createDecipheriv("aes-256-gcm", keyBuffer, iv);
29
+ decipher.setAuthTag(authTag);
30
+ let decrypted = decipher.update(encryptedData.toString("base64"), "base64", "utf8");
31
+ decrypted += decipher.final("utf8");
32
+ return JSON.parse(decrypted);
33
+ } catch {
34
+ throw new Error("Failed to decrypt data");
35
+ }
36
+ }
37
+ export function getEncryptionConfig(event) {
38
+ const config = event ? useRuntimeConfig(event) : useRuntimeConfig();
39
+ return config.nuxtAegis?.tokenRefresh?.encryption || { enabled: false };
40
+ }
41
+ export async function storeRefreshTokenData(tokenHash, data, event) {
42
+ const encryptionConfig = getEncryptionConfig(event);
43
+ let dataToStore;
44
+ if (encryptionConfig.enabled) {
45
+ if (!encryptionConfig.key) {
46
+ throw new Error("[Nuxt Aegis] Encryption is enabled but no encryption key is configured");
47
+ }
48
+ const encrypted = encryptData(data, encryptionConfig.key);
49
+ dataToStore = { encrypted };
50
+ } else {
51
+ dataToStore = data;
52
+ }
53
+ await useStorage("refreshTokenStore").setItem(`${tokenHash}`, dataToStore);
54
+ }
55
+ export async function getRefreshTokenData(tokenHash, event) {
56
+ const encryptionConfig = getEncryptionConfig(event);
57
+ const storedData = await useStorage("refreshTokenStore").getItem(`${tokenHash}`);
58
+ if (!storedData) {
59
+ return null;
60
+ }
61
+ if ("encrypted" in storedData && typeof storedData.encrypted === "string") {
62
+ if (!encryptionConfig.key) {
63
+ throw new Error("[Nuxt Aegis] Data is encrypted but no encryption key is configured");
64
+ }
65
+ return decryptData(storedData.encrypted, encryptionConfig.key);
66
+ }
67
+ return storedData;
68
+ }
69
+ export async function deleteRefreshTokenData(tokenHash) {
70
+ await useStorage("refreshTokenStore").removeItem(`${tokenHash}`);
71
+ }
72
+ export async function revokeRefreshToken(tokenHash, event) {
73
+ const data = await getRefreshTokenData(tokenHash, event);
74
+ if (!data) {
75
+ return;
76
+ }
77
+ data.isRevoked = true;
78
+ await storeRefreshTokenData(tokenHash, data, event);
79
+ }
80
+ export async function generateAndStoreRefreshToken(providerUserInfo, provider, config, previousTokenHash, event) {
81
+ const refreshToken = randomBytes(32).toString("base64url");
82
+ const expiresIn = config.cookie?.maxAge || 604800;
83
+ await storeRefreshTokenData(hashRefreshToken(refreshToken), {
84
+ sub: String(providerUserInfo.sub || providerUserInfo.email || providerUserInfo.id || ""),
85
+ expiresAt: Date.now() + expiresIn * 1e3,
86
+ isRevoked: false,
87
+ previousTokenHash,
88
+ providerUserInfo,
89
+ // Store complete OAuth provider user data
90
+ provider
91
+ // Store provider name for custom claims refresh
92
+ }, event);
93
+ return refreshToken;
94
+ }
95
+ export async function deleteUserRefreshTokens(email, exceptTokenHash, event) {
96
+ const storage = useStorage("refreshTokenStore");
97
+ const keys = await storage.getKeys();
98
+ const normalizedEmail = email.toLowerCase();
99
+ for (const key of keys) {
100
+ if (key === exceptTokenHash) continue;
101
+ const data = await getRefreshTokenData(key, event);
102
+ if (!data) continue;
103
+ const userEmail = data.providerUserInfo?.email?.toLowerCase();
104
+ if (userEmail === normalizedEmail) {
105
+ await deleteRefreshTokenData(key);
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,12 @@
1
+ export interface ResetSessionData {
2
+ email: string;
3
+ expiresAt: number;
4
+ }
5
+ /**
6
+ * Create a password reset session
7
+ */
8
+ export declare function createResetSession(email: string, ttl?: number): Promise<string>;
9
+ /**
10
+ * Validate and delete a password reset session
11
+ */
12
+ export declare function validateAndDeleteResetSession(sessionId: string): Promise<string | null>;