@peterbud/nuxt-aegis 1.1.0-alpha.4 → 1.1.0-alpha.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -61,12 +61,12 @@ export default defineNuxtConfig({
61
61
 
62
62
  nuxtAegis: {
63
63
  token: {
64
- secret: process.env.NUXT_AEGIS_TOKEN_SECRET!,
64
+ secret: process.env.NUXT_NUXT_AEGIS_TOKEN_SECRET!,
65
65
  },
66
66
  providers: {
67
67
  google: {
68
- clientId: process.env.GOOGLE_CLIENT_ID!,
69
- clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
68
+ clientId: process.env.NUXT_NUXT_AEGIS_PROVIDERS_GOOGLE_CLIENT_ID!,
69
+ clientSecret: process.env.NUXT_NUXT_AEGIS_PROVIDERS_GOOGLE_CLIENT_SECRET!,
70
70
  },
71
71
  },
72
72
  },
@@ -114,6 +114,7 @@ const { user, isLoggedIn, login, logout } = useAuth()
114
114
 
115
115
  Ready to dive deeper? Check out the full documentation:
116
116
 
117
+ - **[Documentation Home](https://peterbud.github.io/nuxt-aegis/)** - Main documentation site.
117
118
  - **[Getting Started](https://peterbud.github.io/nuxt-aegis/getting-started/installation)** - Installation and setup guides.
118
119
  - **[Architecture](https://peterbud.github.io/nuxt-aegis/architecture/)** - Overview of the system architecture.
119
120
  - **[Providers](https://peterbud.github.io/nuxt-aegis/providers/)** - Configure Google, GitHub, Auth0, Password, and Mock providers.
package/dist/module.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nuxt-aegis",
3
3
  "configKey": "nuxtAegis",
4
- "version": "1.1.0-alpha.4",
4
+ "version": "1.1.0-alpha.6",
5
5
  "builder": {
6
6
  "@nuxt/module-builder": "1.0.2",
7
7
  "unbuild": "unknown"
package/dist/module.mjs CHANGED
@@ -74,6 +74,11 @@ const module$1 = defineNuxtModule({
74
74
  logging: {
75
75
  level: "info",
76
76
  security: false
77
+ },
78
+ impersonation: {
79
+ enabled: false,
80
+ tokenExpiration: 900,
81
+ originalUserLookupClaim: "sub"
77
82
  }
78
83
  },
79
84
  setup(options, nuxt) {
@@ -225,7 +230,7 @@ const module$1 = defineNuxtModule({
225
230
  if (cm.global) {
226
231
  const userPublicRoutes = cm.publicRoutes || [];
227
232
  const redirectRoutes = [cm.redirectTo, cm.loggedOutRedirectTo];
228
- const allPublicRoutes = [.../* @__PURE__ */ new Set([...userPublicRoutes, ...redirectRoutes])];
233
+ const allPublicRoutes = [...new Set([...userPublicRoutes, ...redirectRoutes].filter((route) => Boolean(route)))];
229
234
  options.clientMiddleware.publicRoutes = allPublicRoutes;
230
235
  if (allPublicRoutes.length === 0) {
231
236
  throw new Error(
@@ -17,7 +17,7 @@ interface UseAuthReturn<T extends BaseTokenClaims = BaseTokenClaims> {
17
17
  isImpersonating: ComputedRef<boolean>;
18
18
  /** Reactive property containing original user data when impersonating */
19
19
  originalUser: ComputedRef<{
20
- originalUserId: string;
20
+ originalUserSub: string;
21
21
  originalUserEmail?: string;
22
22
  originalUserName?: string;
23
23
  } | null>;
@@ -19,7 +19,7 @@ export function useAuth() {
19
19
  return null;
20
20
  }
21
21
  return {
22
- originalUserId: impersonation.originalUserId,
22
+ originalUserSub: impersonation.originalUserSub,
23
23
  originalUserEmail: impersonation.originalUserEmail,
24
24
  originalUserName: impersonation.originalUserName
25
25
  };
@@ -125,7 +125,7 @@ export function useAuth() {
125
125
  authState.value.error = null;
126
126
  logger.debug("Impersonation started successfully", {
127
127
  targetUser: payload.sub,
128
- originalUser: payload.impersonation?.originalUserId
128
+ originalUser: payload.impersonation?.originalUserSub
129
129
  });
130
130
  }
131
131
  }
@@ -82,7 +82,7 @@ export default defineEventHandler(async (event) => {
82
82
  event.context.user = userData;
83
83
  if (payload.impersonation) {
84
84
  event.context.originalUser = {
85
- sub: payload.impersonation.originalUserId,
85
+ sub: payload.impersonation.originalUserSub,
86
86
  email: payload.impersonation.originalUserEmail,
87
87
  name: payload.impersonation.originalUserName
88
88
  };
@@ -47,7 +47,7 @@ export default defineEventHandler(async (event) => {
47
47
  domain: cookieConfig?.domain
48
48
  });
49
49
  logger.security("Impersonation ended", {
50
- originalUser: currentToken.impersonation?.originalUserId,
50
+ originalUser: currentToken.impersonation?.originalUserSub,
51
51
  wasImpersonating: currentToken.sub
52
52
  });
53
53
  return { accessToken };
@@ -23,10 +23,11 @@ export declare function fetchTargetUser(requester: BaseTokenClaims, targetUserId
23
23
  * @param requester - The user performing impersonation
24
24
  * @param targetUserData - Target user data from database
25
25
  * @param reason - Optional reason for impersonation
26
+ * @param originalUserLookupId - Original user lookup ID used for restoration
26
27
  * @param _event - H3 event for context
27
28
  * @returns JWT access token (no refresh token)
28
29
  */
29
- export declare function generateImpersonatedToken(requester: BaseTokenClaims, targetUserData: Record<string, unknown>, reason: string | undefined, _event: H3Event): Promise<string>;
30
+ export declare function generateImpersonatedToken(requester: BaseTokenClaims, targetUserData: Record<string, unknown>, reason: string | undefined, originalUserLookupId: string, _event: H3Event): Promise<string>;
30
31
  /**
31
32
  * Start impersonation session
32
33
  * @param requester - The user requesting impersonation (must be admin)
@@ -21,6 +21,18 @@ function getClientInfo(event) {
21
21
  const userAgent = headers["user-agent"];
22
22
  return { ip, userAgent };
23
23
  }
24
+ function resolveOriginalUserLookupId(requester) {
25
+ const config = useRuntimeConfig();
26
+ const lookupClaim = config.nuxtAegis?.impersonation?.originalUserLookupClaim || "sub";
27
+ const lookupValue = requester[lookupClaim];
28
+ if (typeof lookupValue !== "string" || lookupValue.length === 0) {
29
+ throw createError({
30
+ statusCode: 500,
31
+ message: `Impersonation originalUserLookupClaim "${lookupClaim}" must resolve to a non-empty string on the requester token`
32
+ });
33
+ }
34
+ return lookupValue;
35
+ }
24
36
  export async function checkImpersonationAllowed(requester, targetUserId, event) {
25
37
  checkImpersonationEnabled();
26
38
  if (requester.impersonation) {
@@ -75,7 +87,7 @@ export async function fetchTargetUser(requester, targetUserId, event) {
75
87
  });
76
88
  }
77
89
  }
78
- export async function generateImpersonatedToken(requester, targetUserData, reason, _event) {
90
+ export async function generateImpersonatedToken(requester, targetUserData, reason, originalUserLookupId, _event) {
79
91
  const config = useRuntimeConfig();
80
92
  const tokenConfig = config.nuxtAegis?.token;
81
93
  const impersonationConfig = config.nuxtAegis?.impersonation;
@@ -93,7 +105,8 @@ export async function generateImpersonatedToken(requester, targetUserData, reaso
93
105
  }
94
106
  }
95
107
  const impersonationContext = {
96
- originalUserId: requester.sub,
108
+ originalUserSub: requester.sub,
109
+ originalUserLookupId,
97
110
  originalUserEmail: requester.email,
98
111
  originalUserName: requester.name,
99
112
  impersonatedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -132,7 +145,8 @@ export async function generateImpersonatedToken(requester, targetUserData, reaso
132
145
  export async function startImpersonation(requester, targetUserId, reason, event) {
133
146
  await checkImpersonationAllowed(requester, targetUserId, event);
134
147
  const targetUserData = await fetchTargetUser(requester, targetUserId, event);
135
- const accessToken = await generateImpersonatedToken(requester, targetUserData, reason, event);
148
+ const originalUserLookupId = resolveOriginalUserLookupId(requester);
149
+ const accessToken = await generateImpersonatedToken(requester, targetUserData, reason, originalUserLookupId, event);
136
150
  const { ip, userAgent } = getClientInfo(event);
137
151
  const targetPayload = {
138
152
  sub: targetUserData.sub || targetUserData.id || targetUserData.email,
@@ -170,14 +184,14 @@ export async function endImpersonation(currentToken, event) {
170
184
  originalUserData = await fetchTargetUser(
171
185
  currentToken,
172
186
  // Pass current token as requester (for context)
173
- impersonation.originalUserId,
187
+ impersonation.originalUserLookupId,
174
188
  event
175
189
  );
176
190
  } catch (error) {
177
191
  const err = error;
178
192
  if (err.statusCode === 404) {
179
193
  logger.warn("Original user not found in database, using stored context", {
180
- userId: impersonation.originalUserId
194
+ userLookupId: impersonation.originalUserLookupId
181
195
  });
182
196
  } else {
183
197
  throw error;
@@ -192,7 +206,7 @@ export async function endImpersonation(currentToken, event) {
192
206
  });
193
207
  }
194
208
  const originalPayload = {
195
- sub: originalUserData ? originalUserData.sub || originalUserData.id || originalUserData.email : impersonation.originalUserId,
209
+ sub: originalUserData ? originalUserData.sub || originalUserData.id || originalUserData.email : impersonation.originalUserSub,
196
210
  email: originalUserData ? originalUserData.email : impersonation.originalUserEmail,
197
211
  name: originalUserData ? originalUserData.name : impersonation.originalUserName,
198
212
  picture: originalUserData?.picture,
@@ -51,6 +51,8 @@ export interface ImpersonationConfig {
51
51
  enabled?: boolean;
52
52
  /** Token expiration time for impersonated sessions in seconds (default: 900 = 15 minutes) */
53
53
  tokenExpiration?: number;
54
+ /** Token claim name used to resolve the original user when ending impersonation (default: 'sub') */
55
+ originalUserLookupClaim?: string;
54
56
  }
55
57
  /**
56
58
  * Runtime config for Nuxt Aegis
@@ -7,8 +7,10 @@
7
7
  * Contains essential information about the original user when impersonating
8
8
  */
9
9
  export interface ImpersonationContext {
10
- /** Original user ID (sub) who is performing the impersonation */
11
- originalUserId: string;
10
+ /** Original user subject (`sub`) who is performing the impersonation */
11
+ originalUserSub: string;
12
+ /** Original user lookup ID used to refetch the user when ending impersonation */
13
+ originalUserLookupId: string;
12
14
  /** Original user email */
13
15
  originalUserEmail?: string;
14
16
  /** Original user name */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peterbud/nuxt-aegis",
3
- "version": "1.1.0-alpha.4",
3
+ "version": "1.1.0-alpha.6",
4
4
  "description": "Nuxt module for authentication with JWT token generation and session management.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -52,30 +52,25 @@
52
52
  "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
53
53
  },
54
54
  "dependencies": {
55
- "@nuxt/kit": "^4.3.0",
55
+ "@nuxt/kit": "^4.4.6",
56
56
  "consola": "^3.4.2",
57
- "defu": "^6.1.4",
58
- "jose": "^6.1.3",
59
- "ufo": "^1.6.3"
57
+ "defu": "^6.1.7",
58
+ "jose": "^6.2.3",
59
+ "ufo": "^1.6.4"
60
60
  },
61
61
  "devDependencies": {
62
- "@nuxt/devtools": "^3.1.1",
63
- "@nuxt/eslint-config": "^1.13.0",
62
+ "@nuxt/devtools": "^3.2.4",
63
+ "@nuxt/eslint-config": "^1.15.2",
64
64
  "@nuxt/module-builder": "^1.0.2",
65
- "@nuxt/schema": "^4.3.0",
66
- "@nuxt/test-utils": "^3.23.0",
65
+ "@nuxt/schema": "^4.4.6",
66
+ "@nuxt/test-utils": "^4.0.3",
67
67
  "@types/node": "latest",
68
68
  "changelogen": "^0.6.2",
69
- "eslint": "^9.39.2",
70
- "nuxt": "^4.3.0",
71
- "typescript": "~5.9.3",
72
- "vitest": "^3.2.4",
73
- "vue-tsc": "^3.2.3"
74
- },
75
- "pnpm": {
76
- "overrides": {
77
- "vue-router": "4.4.5"
78
- }
69
+ "eslint": "^10.4.0",
70
+ "nuxt": "^4.4.6",
71
+ "typescript": "~6.0.3",
72
+ "vitest": "^4.1.7",
73
+ "vue-tsc": "^3.3.1"
79
74
  },
80
75
  "packageManager": "pnpm@10.17.1"
81
76
  }