@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,72 @@
1
+ import type { OAuthConfig, OAuthProviderConfig } from '../../types/index.js';
2
+ type ProviderKey = 'google' | 'microsoft' | 'github' | 'auth0' | 'mock';
3
+ /**
4
+ * Type mapping from provider keys to their configuration types
5
+ */
6
+ type ProviderConfigMap = {
7
+ google: import('../../types').GoogleProviderConfig;
8
+ microsoft: import('../../types').MicrosoftProviderConfig;
9
+ github: import('../../types').GithubProviderConfig;
10
+ auth0: import('../../types').Auth0ProviderConfig;
11
+ mock: import('../../types').MockProviderConfig;
12
+ };
13
+ /**
14
+ * Validates and filters custom authorization parameters
15
+ * Removes protected OAuth parameters and logs warnings when they are attempted
16
+ *
17
+ * @param authorizationParams - Custom parameters from configuration
18
+ * @param providerKey - Provider identifier for logging
19
+ * @returns Filtered parameters safe to merge with OAuth query
20
+ */
21
+ export declare function validateAuthorizationParams(authorizationParams: Record<string, string> | undefined, providerKey: string): Record<string, string>;
22
+ /**
23
+ * OAuth provider implementation interface
24
+ * Defines the structure required for OAuth provider implementations
25
+ */
26
+ export interface OAuthProviderImplementation<TKey extends ProviderKey = ProviderKey> {
27
+ /** Default configuration for the provider */
28
+ defaultConfig: Partial<OAuthProviderConfig>;
29
+ /** Authorization URL for the provider */
30
+ authorizeUrl: string;
31
+ /** Token exchange URL for the provider */
32
+ tokenUrl: string;
33
+ /** User info URL for the provider */
34
+ userInfoUrl: string;
35
+ /** Runtime config key for this provider (e.g., 'google', 'microsoft') */
36
+ runtimeConfigKey: TKey;
37
+ /** Extract user info from provider response */
38
+ extractUser: (userResponse: unknown) => {
39
+ [key: string]: unknown;
40
+ };
41
+ /** Build authorization URL query parameters */
42
+ buildAuthQuery: (config: OAuthProviderConfig, redirectUri: string, state?: string) => Record<string, string>;
43
+ /** Build token exchange body parameters */
44
+ buildTokenBody: (config: OAuthProviderConfig, code: string, redirectUri: string) => Record<string, string>;
45
+ }
46
+ /**
47
+ * Helper function to create a properly typed OAuth provider implementation
48
+ * Infers the provider key from the runtimeConfigKey property
49
+ */
50
+ export declare function defineOAuthProvider<TKey extends ProviderKey>(implementation: OAuthProviderImplementation<TKey>): OAuthProviderImplementation<TKey>;
51
+ /**
52
+ * Base OAuth event handler that provides common OAuth flow functionality
53
+ *
54
+ * Handles the complete OAuth 2.0 authorization code flow with CODE-based token delivery:
55
+ *
56
+ * Initial Request (no code parameter):
57
+ * - Redirect user to provider's authorization page
58
+ * - Initiate OAuth flow
59
+ *
60
+ * Callback Request (with code parameter from provider):
61
+ * 1. Exchange authorization code for provider tokens
62
+ * 2. Validate tokens and extract user information
63
+ * 3. Generate cryptographically secure authorization CODE
64
+ * 4. Store CODE with user data (60s expiration, configurable)
65
+ * 5. Redirect to /auth/callback with CODE as query parameter
66
+ *
67
+ * Error Handling:
68
+ * - Generic error redirect to prevent information leakage
69
+ * - Security event logging for all failures
70
+ */
71
+ export declare function defineOAuthEventHandler<TKey extends ProviderKey, TConfig extends ProviderConfigMap[TKey] = ProviderConfigMap[TKey]>(implementation: OAuthProviderImplementation<TKey>, { config, onError, customClaims: _customClaims, onUserInfo: _onUserInfo, onSuccess: _onSuccess, }: OAuthConfig<TConfig>): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
72
+ export {};
@@ -0,0 +1,183 @@
1
+ import { eventHandler, getRequestURL, getQuery, sendRedirect, createError } from "h3";
2
+ import { defu } from "defu";
3
+ import { useNitroApp, useRuntimeConfig } from "#imports";
4
+ import { withQuery } from "ufo";
5
+ import { generateAuthCode, storeAuthCode } from "../utils/authCodeStore.js";
6
+ import { createLogger } from "../utils/logger.js";
7
+ import { useAegisHandler } from "../utils/handler.js";
8
+ const logger = createLogger("OAuth");
9
+ const PROTECTED_PARAMS = ["client_id", "redirect_uri", "code", "grant_type"];
10
+ export function validateAuthorizationParams(authorizationParams, providerKey) {
11
+ if (!authorizationParams) {
12
+ return {};
13
+ }
14
+ const filtered = {};
15
+ const protectedFound = [];
16
+ for (const [key, value] of Object.entries(authorizationParams)) {
17
+ if (PROTECTED_PARAMS.includes(key)) {
18
+ protectedFound.push(key);
19
+ } else {
20
+ filtered[key] = value;
21
+ }
22
+ }
23
+ if (protectedFound.length > 0) {
24
+ logger.warn(`Protected OAuth parameters cannot be overridden in authorizationParams for ${providerKey}:`, {
25
+ attempted: protectedFound,
26
+ protected: PROTECTED_PARAMS,
27
+ message: "These parameters are ignored for security reasons"
28
+ });
29
+ }
30
+ return filtered;
31
+ }
32
+ export function defineOAuthProvider(implementation) {
33
+ return implementation;
34
+ }
35
+ function getOAuthRedirectUri(event) {
36
+ const requestURL = getRequestURL(event);
37
+ return `${requestURL.protocol}//${requestURL.host}${requestURL.pathname}`;
38
+ }
39
+ export function defineOAuthEventHandler(implementation, {
40
+ config,
41
+ onError,
42
+ customClaims: _customClaims,
43
+ onUserInfo: _onUserInfo,
44
+ onSuccess: _onSuccess
45
+ }) {
46
+ return eventHandler(async (event) => {
47
+ try {
48
+ const runtimeConfig = useRuntimeConfig(event).nuxtAegis;
49
+ const providerRuntimeConfig = runtimeConfig.providers?.[implementation.runtimeConfigKey] || {};
50
+ const mergedConfig = defu(config, providerRuntimeConfig, implementation.defaultConfig);
51
+ if (!mergedConfig.clientId || !mergedConfig.clientSecret) {
52
+ throw createError({
53
+ statusCode: 500,
54
+ statusMessage: "Internal Server Error",
55
+ message: `OAuth provider ${implementation.runtimeConfigKey} is not properly configured`
56
+ });
57
+ }
58
+ const query = getQuery(event);
59
+ if (query.error) {
60
+ throw createError({
61
+ statusCode: 400,
62
+ statusMessage: "OAuth Error",
63
+ message: `OAuth provider returned error: ${query.error}`
64
+ });
65
+ }
66
+ const redirectUri = mergedConfig.redirectUri || getOAuthRedirectUri(event);
67
+ if (!query.code) {
68
+ const authQuery = implementation.buildAuthQuery(mergedConfig, redirectUri, query.state);
69
+ return sendRedirect(event, withQuery(implementation.authorizeUrl, authQuery));
70
+ }
71
+ const tokenBody = implementation.buildTokenBody(mergedConfig, query.code, redirectUri);
72
+ const tokenResponse = await $fetch(implementation.tokenUrl, {
73
+ method: "POST",
74
+ headers: {
75
+ "Content-Type": "application/x-www-form-urlencoded",
76
+ "Accept": "application/json"
77
+ },
78
+ body: new URLSearchParams(tokenBody)
79
+ });
80
+ const { access_token, refresh_token, id_token, expires_in } = tokenResponse;
81
+ if (!access_token) {
82
+ throw createError({
83
+ statusCode: 400,
84
+ statusMessage: "OAuth Token Error",
85
+ message: "Failed to obtain access token from provider"
86
+ });
87
+ }
88
+ const userResponse = await $fetch(implementation.userInfoUrl, {
89
+ headers: {
90
+ Authorization: `Bearer ${access_token}`
91
+ }
92
+ });
93
+ let providerUserInfo = implementation.extractUser(userResponse);
94
+ const tokens = { access_token, refresh_token, id_token, expires_in };
95
+ if (_onUserInfo) {
96
+ providerUserInfo = await _onUserInfo(providerUserInfo, tokens, event);
97
+ } else {
98
+ const handler = useAegisHandler();
99
+ if (handler?.onUserInfo) {
100
+ const hookPayload = {
101
+ providerUserInfo,
102
+ tokens,
103
+ provider: implementation.runtimeConfigKey,
104
+ event
105
+ };
106
+ const transformedUser = await handler.onUserInfo(hookPayload);
107
+ if (transformedUser) {
108
+ providerUserInfo = transformedUser;
109
+ }
110
+ }
111
+ }
112
+ let resolvedCustomClaims;
113
+ if (_customClaims) {
114
+ if (typeof _customClaims === "function") {
115
+ resolvedCustomClaims = await _customClaims(providerUserInfo, tokens);
116
+ } else {
117
+ resolvedCustomClaims = _customClaims;
118
+ }
119
+ }
120
+ if (_onSuccess) {
121
+ await _onSuccess({
122
+ providerUserInfo,
123
+ tokens,
124
+ provider: implementation.runtimeConfigKey,
125
+ event
126
+ });
127
+ }
128
+ const nitroApp = useNitroApp();
129
+ const successPayload = {
130
+ providerUserInfo,
131
+ tokens,
132
+ provider: implementation.runtimeConfigKey,
133
+ event
134
+ };
135
+ await nitroApp.hooks.callHook("nuxt-aegis:success", successPayload);
136
+ try {
137
+ const authCode = generateAuthCode();
138
+ const authCodeExpiresIn = runtimeConfig.authCode?.expiresIn || 60;
139
+ await storeAuthCode(
140
+ authCode,
141
+ providerUserInfo,
142
+ tokens,
143
+ implementation.runtimeConfigKey,
144
+ resolvedCustomClaims,
145
+ authCodeExpiresIn,
146
+ event
147
+ );
148
+ logger.security("OAuth authentication successful, redirecting with CODE", {
149
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
150
+ event: "OAUTH_SUCCESS_REDIRECT",
151
+ codePrefix: `${authCode.substring(0, 8)}...`
152
+ });
153
+ const callbackUrl = new URL(runtimeConfig.endpoints?.callbackPath || "/auth/callback", getOAuthRedirectUri(event));
154
+ callbackUrl.searchParams.set("code", authCode);
155
+ return sendRedirect(event, callbackUrl.href);
156
+ } catch (codeError) {
157
+ logger.error("Authorization code generation/storage failed", {
158
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
159
+ event: "CODE_GENERATION_ERROR",
160
+ error: import.meta.dev ? codeError : "Error details hidden in production",
161
+ severity: "error"
162
+ });
163
+ const errorUrl = new URL(runtimeConfig.endpoints?.callbackPath || "/auth/callback", getOAuthRedirectUri(event));
164
+ errorUrl.searchParams.set("error", "authentication_failed");
165
+ return sendRedirect(event, errorUrl.href);
166
+ }
167
+ } catch (error) {
168
+ logger.error("OAuth authentication error", {
169
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
170
+ event: "OAUTH_AUTH_ERROR",
171
+ error: import.meta.dev ? error : "Error details hidden in production",
172
+ severity: "error"
173
+ });
174
+ if (onError) {
175
+ return await onError(event, error);
176
+ }
177
+ const runtimeConfig = useRuntimeConfig(event).nuxtAegis;
178
+ const errorUrl = new URL(runtimeConfig.endpoints?.callbackPath || "/auth/callback", getOAuthRedirectUri(event));
179
+ errorUrl.searchParams.set("error", "authentication_failed");
180
+ return sendRedirect(event, errorUrl.href);
181
+ }
182
+ });
183
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * POST /auth/impersonate
3
+ * Allows privileged users (admins) to impersonate another user
4
+ *
5
+ * Request body:
6
+ * - targetUserId: string (required) - The ID of the user to impersonate
7
+ * - reason: string (optional) - Reason for impersonation
8
+ *
9
+ * Response:
10
+ * - accessToken: string - JWT for impersonated session (no refresh token)
11
+ *
12
+ * Requirements:
13
+ * - Impersonation feature must be enabled in config
14
+ * - Requester must pass permission check (default: admin role)
15
+ * - Target user must exist
16
+ * - Cannot impersonate while already impersonating
17
+ */
18
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
19
+ accessToken: string;
20
+ }>>;
21
+ export default _default;
@@ -0,0 +1,68 @@
1
+ import { defineEventHandler, readBody, createError, getHeader } from "h3";
2
+ import { verifyToken } from "../utils/jwt.js";
3
+ import { startImpersonation } from "../utils/impersonation.js";
4
+ import { useRuntimeConfig } from "#imports";
5
+ import { createLogger } from "../utils/logger.js";
6
+ const logger = createLogger("Impersonate");
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 currentUser = await verifyToken(token, tokenConfig.secret);
32
+ if (!currentUser) {
33
+ throw createError({
34
+ statusCode: 401,
35
+ message: "Invalid or expired token"
36
+ });
37
+ }
38
+ const body = await readBody(event);
39
+ if (!body || !body.targetUserId) {
40
+ throw createError({
41
+ statusCode: 400,
42
+ message: "targetUserId is required"
43
+ });
44
+ }
45
+ const accessToken = await startImpersonation(
46
+ currentUser,
47
+ body.targetUserId,
48
+ body.reason,
49
+ event
50
+ );
51
+ logger.security("Impersonation started", {
52
+ admin: currentUser.sub,
53
+ target: body.targetUserId,
54
+ reason: body.reason
55
+ });
56
+ return { accessToken };
57
+ } catch (error) {
58
+ logger.error("Impersonation failed:", error);
59
+ const err = error;
60
+ if (err.statusCode) {
61
+ throw error;
62
+ }
63
+ throw createError({
64
+ statusCode: 500,
65
+ message: err.message || "Failed to start impersonation"
66
+ });
67
+ }
68
+ });
@@ -0,0 +1,9 @@
1
+ /**
2
+ * POST /auth/logout
3
+ * Ends the user session by clearing authentication cookies and revoking refresh token
4
+ */
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
6
+ success: boolean;
7
+ message: string;
8
+ }>>;
9
+ export default _default;
@@ -0,0 +1,24 @@
1
+ import { defineEventHandler, getCookie } from "h3";
2
+ import { hashRefreshToken, revokeRefreshToken } from "../utils/refreshToken.js";
3
+ import { clearToken } from "../utils/cookies.js";
4
+ import { useRuntimeConfig } from "#imports";
5
+ import { createLogger } from "../utils/logger.js";
6
+ const logger = createLogger("Logout");
7
+ export default defineEventHandler(async (event) => {
8
+ const config = useRuntimeConfig(event);
9
+ const cookieConfig = config.nuxtAegis?.tokenRefresh?.cookie;
10
+ try {
11
+ const cookieName = cookieConfig?.cookieName || "nuxt-aegis-refresh";
12
+ const refreshToken = getCookie(event, cookieName);
13
+ if (refreshToken) {
14
+ const hashedRefreshToken = hashRefreshToken(refreshToken);
15
+ await revokeRefreshToken(hashedRefreshToken, event);
16
+ }
17
+ clearToken(event, cookieConfig);
18
+ return { success: true, message: "Logout successful" };
19
+ } catch (error) {
20
+ logger.error("Logout error:", error);
21
+ clearToken(event, cookieConfig);
22
+ return { success: true, message: "Logout completed" };
23
+ }
24
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * GET /api/user/me
3
+ * Returns the current authenticated user's information
4
+ */
5
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<import("../../types/index.js").TokenPayload>>;
6
+ export default _default;
@@ -0,0 +1,11 @@
1
+ import { defineEventHandler, createError } from "h3";
2
+ export default defineEventHandler(async (event) => {
3
+ if (event.context.user) {
4
+ return event.context.user;
5
+ }
6
+ throw createError({
7
+ statusCode: 401,
8
+ statusMessage: "Unauthorized",
9
+ message: "Authentication required"
10
+ });
11
+ });
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Mock OAuth Authorization Endpoint
3
+ * Simulates: OAuth provider's authorization endpoint
4
+ *
5
+ * DEVELOPMENT/TEST ONLY
6
+ *
7
+ * This simulates an OAuth provider's authorization flow.
8
+ * In real OAuth, users would see a login/consent page.
9
+ * For testing, we auto-approve and redirect back immediately.
10
+ *
11
+ * Query Parameters:
12
+ * - response_type: Should be 'code'
13
+ * - client_id: OAuth client ID
14
+ * - redirect_uri: Where to redirect back
15
+ * - scope: Requested scopes
16
+ * - state: Optional state parameter for CSRF protection
17
+ * - user: (Optional) User persona identifier from mockUsers config
18
+ * - mock_error: (Optional) Simulate OAuth error response
19
+ *
20
+ * Error Simulation:
21
+ * - ?mock_error=access_denied - User denied access
22
+ * - ?mock_error=invalid_request - Invalid request parameters
23
+ * - ?mock_error=unauthorized_client - Client not authorized
24
+ * - ?mock_error=invalid_scope - Invalid scope requested
25
+ * - ?mock_error=server_error - Server error
26
+ * - ?mock_error=temporarily_unavailable - Service temporarily unavailable
27
+ */
28
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<void>>;
29
+ export default _default;
@@ -0,0 +1,103 @@
1
+ import { defineEventHandler, getQuery, sendRedirect, createError } from "h3";
2
+ import { withQuery } from "ufo";
3
+ import { useRuntimeConfig } from "#imports";
4
+ import { generateMockCode, storeMockCode } from "../../utils/mockCodeStore.js";
5
+ import { createLogger } from "../../utils/logger.js";
6
+ const logger = createLogger("MockAuthorize");
7
+ export default defineEventHandler(async (event) => {
8
+ const query = getQuery(event);
9
+ const runtimeConfig = useRuntimeConfig(event);
10
+ const mockConfig = runtimeConfig.nuxtAegis?.providers?.mock;
11
+ if (!mockConfig) {
12
+ throw createError({
13
+ statusCode: 500,
14
+ statusMessage: "Internal Server Error",
15
+ message: "[nuxt-aegis] Mock provider not configured"
16
+ });
17
+ }
18
+ if (!query.client_id) {
19
+ throw createError({
20
+ statusCode: 400,
21
+ statusMessage: "Bad Request",
22
+ message: "Missing required parameter: client_id"
23
+ });
24
+ }
25
+ if (!query.redirect_uri || typeof query.redirect_uri !== "string") {
26
+ throw createError({
27
+ statusCode: 400,
28
+ statusMessage: "Bad Request",
29
+ message: "Missing required parameter: redirect_uri"
30
+ });
31
+ }
32
+ if (query.response_type !== "code") {
33
+ throw createError({
34
+ statusCode: 400,
35
+ statusMessage: "Bad Request",
36
+ message: 'Invalid response_type. Must be "code"'
37
+ });
38
+ }
39
+ if (query.mock_error) {
40
+ const validErrors = [
41
+ "access_denied",
42
+ "invalid_request",
43
+ "unauthorized_client",
44
+ "invalid_scope",
45
+ "server_error",
46
+ "temporarily_unavailable"
47
+ ];
48
+ const errorCode = query.mock_error;
49
+ const errorDescription = getErrorDescription(errorCode);
50
+ if (!validErrors.includes(errorCode)) {
51
+ console.warn(`[nuxt-aegis] Invalid mock_error: ${errorCode}. Valid errors:`, validErrors);
52
+ }
53
+ return sendRedirect(event, withQuery(query.redirect_uri, {
54
+ error: errorCode,
55
+ error_description: errorDescription,
56
+ state: query.state || ""
57
+ }));
58
+ }
59
+ const userParam = query.user;
60
+ const userId = userParam || mockConfig.defaultUser || Object.keys(mockConfig.mockUsers)[0];
61
+ if (!userId) {
62
+ throw createError({
63
+ statusCode: 500,
64
+ statusMessage: "Internal Server Error",
65
+ message: "[nuxt-aegis] Mock provider has no users configured"
66
+ });
67
+ }
68
+ if (!mockConfig.mockUsers[userId]) {
69
+ throw createError({
70
+ statusCode: 400,
71
+ statusMessage: "Bad Request",
72
+ message: `[nuxt-aegis] Invalid user persona: ${userId}. Available: ${Object.keys(mockConfig.mockUsers).join(", ")}`
73
+ });
74
+ }
75
+ const code = generateMockCode();
76
+ storeMockCode({
77
+ code,
78
+ userId,
79
+ // Store the user persona identifier
80
+ clientId: query.client_id,
81
+ redirectUri: query.redirect_uri
82
+ });
83
+ logger.debug("Generated authorization code:", {
84
+ code: code.substring(0, 20) + "...",
85
+ user: userId,
86
+ clientId: query.client_id
87
+ });
88
+ return sendRedirect(event, withQuery(query.redirect_uri, {
89
+ code,
90
+ state: query.state || ""
91
+ }));
92
+ });
93
+ function getErrorDescription(errorCode) {
94
+ const descriptions = {
95
+ access_denied: "The user denied the authorization request",
96
+ invalid_request: "The request is missing a required parameter or is otherwise malformed",
97
+ unauthorized_client: "The client is not authorized to request an authorization code",
98
+ invalid_scope: "The requested scope is invalid, unknown, or malformed",
99
+ server_error: "The authorization server encountered an unexpected error",
100
+ temporarily_unavailable: "The authorization server is currently unable to handle the request"
101
+ };
102
+ return descriptions[errorCode] || "Mock OAuth error for testing";
103
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Mock OAuth Token Endpoint
3
+ * Simulates: OAuth provider's token exchange endpoint
4
+ *
5
+ * DEVELOPMENT/TEST ONLY
6
+ *
7
+ * Exchanges authorization code for OAuth tokens.
8
+ *
9
+ * Request Body:
10
+ * - code: Authorization code from authorize endpoint
11
+ * - client_id: OAuth client ID
12
+ * - client_secret: OAuth client secret
13
+ * - redirect_uri: Same redirect_uri used in authorize
14
+ * - grant_type: Should be 'authorization_code'
15
+ *
16
+ * Response:
17
+ * - access_token: OAuth access token
18
+ * - refresh_token: OAuth refresh token
19
+ * - id_token: JWT ID token (optional)
20
+ * - expires_in: Token lifetime in seconds
21
+ * - token_type: Always 'Bearer'
22
+ */
23
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
24
+ access_token: string;
25
+ refresh_token: string;
26
+ id_token: string;
27
+ expires_in: number;
28
+ token_type: string;
29
+ scope: string;
30
+ }>>;
31
+ export default _default;
@@ -0,0 +1,88 @@
1
+ import { defineEventHandler, readBody, createError } from "h3";
2
+ import { useRuntimeConfig } from "#imports";
3
+ import { retrieveAndDeleteMockCode, storeMockToken } from "../../utils/mockCodeStore.js";
4
+ import { createLogger } from "../../utils/logger.js";
5
+ const logger = createLogger("MockToken");
6
+ export default defineEventHandler(async (event) => {
7
+ const body = await readBody(event);
8
+ const runtimeConfig = useRuntimeConfig(event);
9
+ const mockConfig = runtimeConfig.nuxtAegis?.providers?.mock;
10
+ if (!mockConfig) {
11
+ throw createError({
12
+ statusCode: 500,
13
+ statusMessage: "Internal Server Error",
14
+ message: "[nuxt-aegis] Mock provider not configured"
15
+ });
16
+ }
17
+ if (!body.code) {
18
+ throw createError({
19
+ statusCode: 400,
20
+ statusMessage: "invalid_request",
21
+ message: "Missing required parameter: code"
22
+ });
23
+ }
24
+ if (!body.client_id) {
25
+ throw createError({
26
+ statusCode: 400,
27
+ statusMessage: "invalid_request",
28
+ message: "Missing required parameter: client_id"
29
+ });
30
+ }
31
+ if (!body.client_secret) {
32
+ throw createError({
33
+ statusCode: 400,
34
+ statusMessage: "invalid_request",
35
+ message: "Missing required parameter: client_secret"
36
+ });
37
+ }
38
+ if (body.grant_type !== "authorization_code") {
39
+ throw createError({
40
+ statusCode: 400,
41
+ statusMessage: "unsupported_grant_type",
42
+ message: 'Invalid grant_type. Must be "authorization_code"'
43
+ });
44
+ }
45
+ const codeData = retrieveAndDeleteMockCode(body.code);
46
+ if (!codeData) {
47
+ throw createError({
48
+ statusCode: 400,
49
+ statusMessage: "invalid_grant",
50
+ message: "Invalid authorization code"
51
+ });
52
+ }
53
+ if (codeData.clientId !== body.client_id) {
54
+ throw createError({
55
+ statusCode: 400,
56
+ statusMessage: "invalid_grant",
57
+ message: "Client ID mismatch"
58
+ });
59
+ }
60
+ if (codeData.redirectUri !== body.redirect_uri) {
61
+ throw createError({
62
+ statusCode: 400,
63
+ statusMessage: "invalid_grant",
64
+ message: "Redirect URI mismatch"
65
+ });
66
+ }
67
+ if (body.client_secret !== mockConfig.clientSecret) {
68
+ throw createError({
69
+ statusCode: 401,
70
+ statusMessage: "invalid_client",
71
+ message: "Invalid client credentials"
72
+ });
73
+ }
74
+ logger.debug("Token exchange successful for user:", codeData.userId);
75
+ const randomBytes = crypto.getRandomValues(new Uint8Array(32));
76
+ const randomHex = Array.from(randomBytes).map((b) => b.toString(16).padStart(2, "0")).join("");
77
+ const accessToken = `mock_aegis_access_${randomHex}`;
78
+ storeMockToken(accessToken, codeData.userId);
79
+ return {
80
+ access_token: accessToken,
81
+ refresh_token: `mock_aegis_refresh_${randomHex.slice(0, 32)}`,
82
+ id_token: `mock.aegis.idtoken.${randomHex.slice(0, 24)}`,
83
+ expires_in: 3600,
84
+ // 1 hour
85
+ token_type: "Bearer",
86
+ scope: mockConfig.scopes?.join(" ") || "openid profile email"
87
+ };
88
+ });
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Mock OAuth UserInfo Endpoint
3
+ * Simulates: OAuth provider's user profile endpoint
4
+ *
5
+ * DEVELOPMENT/TEST ONLY
6
+ *
7
+ * Returns user information based on the access token.
8
+ * The user data is retrieved from the mockUsers configuration
9
+ * using the token->user mapping stored during token exchange.
10
+ *
11
+ * Headers:
12
+ * - Authorization: Bearer <access_token>
13
+ *
14
+ * Response:
15
+ * User profile data from mockUsers configuration, including:
16
+ * - sub: User subject identifier (required)
17
+ * - email: User's email address (required)
18
+ * - name: User's full name (required)
19
+ * - Additional custom claims as defined in mockUsers
20
+ */
21
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
22
+ [key: string]: unknown;
23
+ sub: string;
24
+ email: string;
25
+ name: string;
26
+ }>>;
27
+ export default _default;