@thecodeorigin/auth 0.0.7 → 0.0.9

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 (43) hide show
  1. package/dist/contract/index.d.mts +62 -2
  2. package/dist/contract/index.d.ts +62 -2
  3. package/dist/contract/index.mjs +30 -1
  4. package/dist/module.d.mts +4 -0
  5. package/dist/module.d.ts +4 -0
  6. package/dist/module.json +2 -2
  7. package/dist/module.mjs +25 -5
  8. package/dist/runtime/app/composables/useAuth.d.ts +13 -0
  9. package/dist/runtime/app/composables/useAuth.js +9 -1
  10. package/dist/runtime/app/composables/useCasl.d.ts +9 -6
  11. package/dist/runtime/app/middleware/auth.global.d.ts +1 -1
  12. package/dist/runtime/app/middleware/auth.global.js +16 -5
  13. package/dist/runtime/app/middleware/casl.global.d.ts +1 -1
  14. package/dist/runtime/app/plugins/0.session.d.ts +1 -1
  15. package/dist/runtime/app/plugins/ability.d.ts +2 -2
  16. package/dist/runtime/app/utils/auth-redirect.d.ts +10 -0
  17. package/dist/runtime/app/utils/auth-redirect.js +13 -0
  18. package/dist/runtime/server/api/_auth/impersonate.post.js +1 -0
  19. package/dist/runtime/server/api/_auth/organizations/[orgId]/users.get.d.ts +10 -0
  20. package/dist/runtime/server/api/_auth/organizations/[orgId]/users.get.js +25 -0
  21. package/dist/runtime/server/api/_auth/organizations/switch.post.js +19 -9
  22. package/dist/runtime/server/api/_auth/sign-out.post.js +2 -0
  23. package/dist/runtime/server/index.d.ts +6 -1
  24. package/dist/runtime/server/index.js +13 -1
  25. package/dist/runtime/server/routes/callback.get.js +3 -0
  26. package/dist/runtime/server/routes/sign-in.get.js +2 -0
  27. package/dist/runtime/server/routes/sign-out.get.js +2 -0
  28. package/dist/runtime/server/utils/ability.d.ts +15 -0
  29. package/dist/runtime/server/utils/ability.js +25 -0
  30. package/dist/runtime/server/utils/directory.d.ts +3 -0
  31. package/dist/runtime/server/utils/directory.js +14 -0
  32. package/dist/runtime/server/utils/handlers.d.ts +4 -1
  33. package/dist/runtime/server/utils/handlers.js +34 -4
  34. package/dist/runtime/server/utils/idp.d.ts +2 -0
  35. package/dist/runtime/server/utils/idp.js +11 -0
  36. package/dist/runtime/server/utils/integration-key.d.ts +13 -0
  37. package/dist/runtime/server/utils/integration-key.js +27 -0
  38. package/dist/runtime/server/utils/logout-intent.d.ts +3 -0
  39. package/dist/runtime/server/utils/logout-intent.js +17 -0
  40. package/dist/runtime/server/utils/oidc.d.ts +3 -0
  41. package/dist/runtime/server/utils/session.d.ts +11 -37
  42. package/dist/runtime/server/utils/session.js +49 -16
  43. package/package.json +29 -27
@@ -55,6 +55,66 @@ declare const ImpersonationCandidateSchema: z.ZodObject<{
55
55
  image: z.ZodNullable<z.ZodString>;
56
56
  }, z.core.$strip>;
57
57
  type ImpersonationCandidate = z.infer<typeof ImpersonationCandidateSchema>;
58
+ declare const DirectoryUserSchema: z.ZodObject<{
59
+ sub: z.ZodString;
60
+ email: z.ZodString;
61
+ name: z.ZodNullable<z.ZodString>;
62
+ picture: z.ZodNullable<z.ZodString>;
63
+ }, z.core.$strip>;
64
+ type DirectoryUser = z.infer<typeof DirectoryUserSchema>;
65
+ declare const DirectoryUserPageSchema: z.ZodObject<{
66
+ items: z.ZodArray<z.ZodObject<{
67
+ sub: z.ZodString;
68
+ email: z.ZodString;
69
+ name: z.ZodNullable<z.ZodString>;
70
+ picture: z.ZodNullable<z.ZodString>;
71
+ }, z.core.$strip>>;
72
+ hasMore: z.ZodBoolean;
73
+ }, z.core.$strip>;
74
+ type DirectoryUserPage = z.infer<typeof DirectoryUserPageSchema>;
75
+ declare const OrganizationContextSchema: z.ZodObject<{
76
+ organization: z.ZodObject<{
77
+ id: z.ZodString;
78
+ slug: z.ZodString;
79
+ name: z.ZodString;
80
+ role: z.ZodString;
81
+ personal: z.ZodBoolean;
82
+ }, z.core.$strip>;
83
+ organizations: z.ZodArray<z.ZodObject<{
84
+ id: z.ZodString;
85
+ slug: z.ZodString;
86
+ name: z.ZodString;
87
+ role: z.ZodString;
88
+ personal: z.ZodBoolean;
89
+ }, z.core.$strip>>;
90
+ abilities: z.ZodArray<z.ZodObject<{
91
+ action: z.ZodString;
92
+ subject: z.ZodString;
93
+ conditions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
94
+ }, z.core.$strip>>;
95
+ systemRole: z.ZodNullable<z.ZodString>;
96
+ entitlement: z.ZodNullable<z.ZodObject<{
97
+ product: z.ZodString;
98
+ plan: z.ZodString;
99
+ status: z.ZodString;
100
+ active: z.ZodBoolean;
101
+ currentPeriodEnd: z.ZodNullable<z.ZodNumber>;
102
+ }, z.core.$strip>>;
103
+ }, z.core.$strip>;
104
+ type OrganizationContext = z.infer<typeof OrganizationContextSchema>;
105
+ declare const IntegrationKeyContextSchema: z.ZodObject<{
106
+ keyId: z.ZodString;
107
+ organizationId: z.ZodString;
108
+ clientId: z.ZodString;
109
+ audience: z.ZodLiteral<"vault">;
110
+ resourceType: z.ZodLiteral<"environment">;
111
+ resourceId: z.ZodString;
112
+ permissions: z.ZodArray<z.ZodLiteral<"secrets:read">>;
113
+ name: z.ZodString;
114
+ prefix: z.ZodString;
115
+ expiresAt: z.ZodNullable<z.ZodNumber>;
116
+ }, z.core.$strip>;
117
+ type IntegrationKeyContext = z.infer<typeof IntegrationKeyContextSchema>;
58
118
  /** Server-safe session projection — no tokens, no KV internals. */
59
119
  interface ServerAuthSession {
60
120
  sub: string;
@@ -113,5 +173,5 @@ declare const PublicSessionSchema: z.ZodObject<{
113
173
  }, z.core.$strip>;
114
174
  type PublicSession = z.infer<typeof PublicSessionSchema>;
115
175
 
116
- export { AbilityRuleSchema, ImpersonationCandidateSchema, PublicSessionSchema, PublicUserSchema, RpOrganizationSchema, UserinfoClaimsSchema };
117
- export type { AbilityRule, ImpersonationCandidate, PublicSession, PublicUser, RpOrganization, ServerAuthSession, UserinfoClaims };
176
+ export { AbilityRuleSchema, DirectoryUserPageSchema, DirectoryUserSchema, ImpersonationCandidateSchema, IntegrationKeyContextSchema, OrganizationContextSchema, PublicSessionSchema, PublicUserSchema, RpOrganizationSchema, UserinfoClaimsSchema };
177
+ export type { AbilityRule, DirectoryUser, DirectoryUserPage, ImpersonationCandidate, IntegrationKeyContext, OrganizationContext, PublicSession, PublicUser, RpOrganization, ServerAuthSession, UserinfoClaims };
@@ -55,6 +55,66 @@ declare const ImpersonationCandidateSchema: z.ZodObject<{
55
55
  image: z.ZodNullable<z.ZodString>;
56
56
  }, z.core.$strip>;
57
57
  type ImpersonationCandidate = z.infer<typeof ImpersonationCandidateSchema>;
58
+ declare const DirectoryUserSchema: z.ZodObject<{
59
+ sub: z.ZodString;
60
+ email: z.ZodString;
61
+ name: z.ZodNullable<z.ZodString>;
62
+ picture: z.ZodNullable<z.ZodString>;
63
+ }, z.core.$strip>;
64
+ type DirectoryUser = z.infer<typeof DirectoryUserSchema>;
65
+ declare const DirectoryUserPageSchema: z.ZodObject<{
66
+ items: z.ZodArray<z.ZodObject<{
67
+ sub: z.ZodString;
68
+ email: z.ZodString;
69
+ name: z.ZodNullable<z.ZodString>;
70
+ picture: z.ZodNullable<z.ZodString>;
71
+ }, z.core.$strip>>;
72
+ hasMore: z.ZodBoolean;
73
+ }, z.core.$strip>;
74
+ type DirectoryUserPage = z.infer<typeof DirectoryUserPageSchema>;
75
+ declare const OrganizationContextSchema: z.ZodObject<{
76
+ organization: z.ZodObject<{
77
+ id: z.ZodString;
78
+ slug: z.ZodString;
79
+ name: z.ZodString;
80
+ role: z.ZodString;
81
+ personal: z.ZodBoolean;
82
+ }, z.core.$strip>;
83
+ organizations: z.ZodArray<z.ZodObject<{
84
+ id: z.ZodString;
85
+ slug: z.ZodString;
86
+ name: z.ZodString;
87
+ role: z.ZodString;
88
+ personal: z.ZodBoolean;
89
+ }, z.core.$strip>>;
90
+ abilities: z.ZodArray<z.ZodObject<{
91
+ action: z.ZodString;
92
+ subject: z.ZodString;
93
+ conditions: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
94
+ }, z.core.$strip>>;
95
+ systemRole: z.ZodNullable<z.ZodString>;
96
+ entitlement: z.ZodNullable<z.ZodObject<{
97
+ product: z.ZodString;
98
+ plan: z.ZodString;
99
+ status: z.ZodString;
100
+ active: z.ZodBoolean;
101
+ currentPeriodEnd: z.ZodNullable<z.ZodNumber>;
102
+ }, z.core.$strip>>;
103
+ }, z.core.$strip>;
104
+ type OrganizationContext = z.infer<typeof OrganizationContextSchema>;
105
+ declare const IntegrationKeyContextSchema: z.ZodObject<{
106
+ keyId: z.ZodString;
107
+ organizationId: z.ZodString;
108
+ clientId: z.ZodString;
109
+ audience: z.ZodLiteral<"vault">;
110
+ resourceType: z.ZodLiteral<"environment">;
111
+ resourceId: z.ZodString;
112
+ permissions: z.ZodArray<z.ZodLiteral<"secrets:read">>;
113
+ name: z.ZodString;
114
+ prefix: z.ZodString;
115
+ expiresAt: z.ZodNullable<z.ZodNumber>;
116
+ }, z.core.$strip>;
117
+ type IntegrationKeyContext = z.infer<typeof IntegrationKeyContextSchema>;
58
118
  /** Server-safe session projection — no tokens, no KV internals. */
59
119
  interface ServerAuthSession {
60
120
  sub: string;
@@ -113,5 +173,5 @@ declare const PublicSessionSchema: z.ZodObject<{
113
173
  }, z.core.$strip>;
114
174
  type PublicSession = z.infer<typeof PublicSessionSchema>;
115
175
 
116
- export { AbilityRuleSchema, ImpersonationCandidateSchema, PublicSessionSchema, PublicUserSchema, RpOrganizationSchema, UserinfoClaimsSchema };
117
- export type { AbilityRule, ImpersonationCandidate, PublicSession, PublicUser, RpOrganization, ServerAuthSession, UserinfoClaims };
176
+ export { AbilityRuleSchema, DirectoryUserPageSchema, DirectoryUserSchema, ImpersonationCandidateSchema, IntegrationKeyContextSchema, OrganizationContextSchema, PublicSessionSchema, PublicUserSchema, RpOrganizationSchema, UserinfoClaimsSchema };
177
+ export type { AbilityRule, DirectoryUser, DirectoryUserPage, ImpersonationCandidate, IntegrationKeyContext, OrganizationContext, PublicSession, PublicUser, RpOrganization, ServerAuthSession, UserinfoClaims };
@@ -39,6 +39,35 @@ const ImpersonationCandidateSchema = z.object({
39
39
  name: z.string().nullable(),
40
40
  image: z.string().nullable()
41
41
  });
42
+ const DirectoryUserSchema = z.object({
43
+ sub: z.string().min(1),
44
+ email: z.string().email(),
45
+ name: z.string().nullable(),
46
+ picture: z.string().nullable()
47
+ });
48
+ const DirectoryUserPageSchema = z.object({
49
+ items: z.array(DirectoryUserSchema),
50
+ hasMore: z.boolean()
51
+ });
52
+ const OrganizationContextSchema = z.object({
53
+ organization: RpOrganizationSchema,
54
+ organizations: z.array(RpOrganizationSchema),
55
+ abilities: z.array(AbilityRuleSchema),
56
+ systemRole: z.string().nullable(),
57
+ entitlement: UserinfoClaimsSchema.shape.entitlement
58
+ });
59
+ const IntegrationKeyContextSchema = z.object({
60
+ keyId: z.string().min(1),
61
+ organizationId: z.string().min(1),
62
+ clientId: z.string().min(1),
63
+ audience: z.literal("vault"),
64
+ resourceType: z.literal("environment"),
65
+ resourceId: z.string().uuid(),
66
+ permissions: z.array(z.literal("secrets:read")).length(1),
67
+ name: z.string().min(1).max(100),
68
+ prefix: z.string().min(1),
69
+ expiresAt: z.number().int().positive().nullable()
70
+ });
42
71
  const PublicSessionSchema = z.object({
43
72
  user: PublicUserSchema,
44
73
  abilities: z.array(AbilityRuleSchema),
@@ -49,4 +78,4 @@ const PublicSessionSchema = z.object({
49
78
  impersonator: PublicUserSchema.nullable()
50
79
  });
51
80
 
52
- export { AbilityRuleSchema, ImpersonationCandidateSchema, PublicSessionSchema, PublicUserSchema, RpOrganizationSchema, UserinfoClaimsSchema };
81
+ export { AbilityRuleSchema, DirectoryUserPageSchema, DirectoryUserSchema, ImpersonationCandidateSchema, IntegrationKeyContextSchema, OrganizationContextSchema, PublicSessionSchema, PublicUserSchema, RpOrganizationSchema, UserinfoClaimsSchema };
package/dist/module.d.mts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface AuthRoutes {
4
+ login: string;
4
5
  signIn: string;
5
6
  callback: string;
6
7
  signOut: string;
@@ -12,6 +13,7 @@ interface ModuleOptions {
12
13
  scopes?: string[];
13
14
  sessionStorageBase?: string;
14
15
  sessionCookieName?: string;
16
+ logoutIntentCookieName?: string;
15
17
  routes?: Partial<AuthRoutes>;
16
18
  }
17
19
  declare module '@nuxt/schema' {
@@ -20,6 +22,7 @@ declare module '@nuxt/schema' {
20
22
  clientSecret: string;
21
23
  sessionStorageBase: string;
22
24
  sessionCookieName: string;
25
+ logoutIntentCookieName: string;
23
26
  };
24
27
  }
25
28
  interface PublicRuntimeConfig {
@@ -27,6 +30,7 @@ declare module '@nuxt/schema' {
27
30
  domain: string;
28
31
  clientId: string;
29
32
  routes: AuthRoutes;
33
+ logoutIntentCookieName: string;
30
34
  scopes: string[];
31
35
  };
32
36
  }
package/dist/module.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import * as _nuxt_schema from '@nuxt/schema';
2
2
 
3
3
  interface AuthRoutes {
4
+ login: string;
4
5
  signIn: string;
5
6
  callback: string;
6
7
  signOut: string;
@@ -12,6 +13,7 @@ interface ModuleOptions {
12
13
  scopes?: string[];
13
14
  sessionStorageBase?: string;
14
15
  sessionCookieName?: string;
16
+ logoutIntentCookieName?: string;
15
17
  routes?: Partial<AuthRoutes>;
16
18
  }
17
19
  declare module '@nuxt/schema' {
@@ -20,6 +22,7 @@ declare module '@nuxt/schema' {
20
22
  clientSecret: string;
21
23
  sessionStorageBase: string;
22
24
  sessionCookieName: string;
25
+ logoutIntentCookieName: string;
23
26
  };
24
27
  }
25
28
  interface PublicRuntimeConfig {
@@ -27,6 +30,7 @@ declare module '@nuxt/schema' {
27
30
  domain: string;
28
31
  clientId: string;
29
32
  routes: AuthRoutes;
33
+ logoutIntentCookieName: string;
30
34
  scopes: string[];
31
35
  };
32
36
  }
package/dist/module.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@thecodeorigin/auth",
3
3
  "configKey": "auth",
4
- "version": "0.0.7",
4
+ "version": "0.0.9",
5
5
  "builder": {
6
- "@nuxt/module-builder": "1.0.2",
6
+ "@nuxt/module-builder": "1.0.3",
7
7
  "unbuild": "3.6.1"
8
8
  }
9
9
  }
package/dist/module.mjs CHANGED
@@ -8,20 +8,34 @@ const module$1 = defineNuxtModule({
8
8
  scopes: ["openid", "profile", "email"],
9
9
  sessionStorageBase: "auth",
10
10
  sessionCookieName: "tco_auth",
11
- routes: { signIn: "/auth/sign-in", callback: "/auth/callback", signOut: "/auth/sign-out", home: "/", error: "/auth/sign-in" }
11
+ routes: { login: "/auth/login", signIn: "/auth/sign-in", callback: "/auth/callback", signOut: "/auth/sign-out", home: "/", error: "/auth/sign-in" }
12
12
  },
13
13
  setup(options, nuxt) {
14
14
  const resolver = createResolver(import.meta.url);
15
- const routes = { signIn: "/auth/sign-in", callback: "/auth/callback", signOut: "/auth/sign-out", home: "/", error: "/auth/sign-in", ...options.routes };
15
+ const routes = {
16
+ login: "/auth/login",
17
+ signIn: "/auth/sign-in",
18
+ callback: "/auth/callback",
19
+ signOut: "/auth/sign-out",
20
+ home: "/",
21
+ error: "/auth/sign-in",
22
+ ...options.routes
23
+ };
24
+ const logoutIntentCookieName = options.logoutIntentCookieName ?? `${options.sessionCookieName}_logout`;
25
+ if (routes.login === routes.signIn) {
26
+ throw new Error("[auth] routes.login and routes.signIn must use different paths");
27
+ }
16
28
  nuxt.options.runtimeConfig.auth = defu(nuxt.options.runtimeConfig.auth, {
17
29
  clientSecret: "",
18
30
  sessionStorageBase: options.sessionStorageBase,
19
- sessionCookieName: options.sessionCookieName
31
+ sessionCookieName: options.sessionCookieName,
32
+ logoutIntentCookieName
20
33
  });
21
34
  nuxt.options.runtimeConfig.public.auth = defu(nuxt.options.runtimeConfig.public.auth, {
22
35
  domain: options.domain,
23
36
  clientId: "",
24
37
  routes,
38
+ logoutIntentCookieName,
25
39
  scopes: options.scopes
26
40
  });
27
41
  addServerHandler({ route: routes.signIn, handler: resolver.resolve("./runtime/server/routes/sign-in.get") });
@@ -32,11 +46,17 @@ const module$1 = defineNuxtModule({
32
46
  ["/api/_auth/sign-out", "sign-out.post"],
33
47
  ["/api/_auth/organizations", "organizations.get"],
34
48
  ["/api/_auth/organizations/switch", "organizations/switch.post"],
49
+ ["/api/_auth/organizations/:orgId/users", "organizations/[orgId]/users.get"],
35
50
  ["/api/_auth/impersonatable-users", "impersonatable-users.get"],
36
51
  ["/api/_auth/impersonate", "impersonate.post"],
37
52
  ["/api/_auth/stop-impersonating", "stop-impersonating.post"]
38
- ])
39
- addServerHandler({ route, handler: resolver.resolve(`./runtime/server/api/_auth/${file}`) });
53
+ ]) {
54
+ addServerHandler({
55
+ route,
56
+ handler: resolver.resolve(`./runtime/server/api/_auth/${file}`),
57
+ method: file.endsWith(".post") ? "post" : "get"
58
+ });
59
+ }
40
60
  addServerImportsDir(resolver.resolve("./runtime/server/utils"));
41
61
  addImportsDir(resolver.resolve("./runtime/app/composables"));
42
62
  addRouteMiddleware({ name: "auth", path: resolver.resolve("./runtime/app/middleware/auth.global"), global: true });
@@ -91,6 +91,19 @@ export declare function useAuth(): {
91
91
  isImpersonating: import("vue").ComputedRef<boolean>;
92
92
  getOrganizations: () => RpOrganization[];
93
93
  switchOrganization: (orgId: string) => Promise<void>;
94
+ getOrganizationUsers: (organizationId: string, query?: {
95
+ q?: string;
96
+ limit?: number;
97
+ offset?: number;
98
+ }) => Promise<{
99
+ items: {
100
+ sub: string;
101
+ email: string;
102
+ name: string | null;
103
+ picture: string | null;
104
+ }[];
105
+ hasMore: boolean;
106
+ }>;
94
107
  getImpersonatableUsers: (query?: {
95
108
  q?: string;
96
109
  limit?: number;
@@ -20,6 +20,12 @@ export function useAuth() {
20
20
  body: { organizationId: orgId }
21
21
  });
22
22
  }
23
+ function getOrganizationUsers(organizationId, query) {
24
+ return $fetch(
25
+ `/api/_auth/organizations/${encodeURIComponent(organizationId)}/users`,
26
+ { query }
27
+ );
28
+ }
23
29
  function getImpersonatableUsers(query) {
24
30
  return $fetch("/api/_auth/impersonatable-users", { query });
25
31
  }
@@ -42,7 +48,8 @@ export function useAuth() {
42
48
  return navigateTo(qs ? `${routes.signIn}?${qs}` : routes.signIn, { external: true });
43
49
  }
44
50
  async function signOut() {
45
- await $fetch("/api/_auth/sign-out", { method: "POST" }).catch(() => {
51
+ const signOutRoute = "/api/_auth/sign-out";
52
+ await $fetch(signOutRoute, { method: "POST" }).catch(() => {
46
53
  });
47
54
  session.value = null;
48
55
  }
@@ -55,6 +62,7 @@ export function useAuth() {
55
62
  isImpersonating,
56
63
  getOrganizations,
57
64
  switchOrganization,
65
+ getOrganizationUsers,
58
66
  getImpersonatableUsers,
59
67
  impersonate,
60
68
  stopImpersonating,
@@ -1,8 +1,11 @@
1
+ import type { Subject } from '@casl/ability';
2
+ interface BrowserAbility {
3
+ can: (action: string, subject: Subject, field?: string) => boolean;
4
+ cannot: (action: string, subject: Subject, field?: string) => boolean;
5
+ }
1
6
  export declare function useCasl(): {
2
- ability: {
3
- can: (action: string, subject: string, field?: string) => boolean;
4
- cannot: (action: string, subject: string, field?: string) => boolean;
5
- };
6
- can: (action: string, subject: string, field?: string) => boolean;
7
- cannot: (action: string, subject: string, field?: string) => boolean;
7
+ ability: BrowserAbility;
8
+ can: (action: string, subject: Subject, field?: string) => boolean;
9
+ cannot: (action: string, subject: Subject, field?: string) => boolean;
8
10
  };
11
+ export {};
@@ -1,2 +1,2 @@
1
- declare const _default: import("#app").RouteMiddleware;
1
+ declare const _default: import("nuxt/app").RouteMiddleware;
2
2
  export default _default;
@@ -1,11 +1,22 @@
1
- import { defineNuxtRouteMiddleware, navigateTo, useRuntimeConfig, useState } from "#app";
1
+ import { defineNuxtRouteMiddleware, navigateTo, useCookie, useRuntimeConfig, useState } from "#app";
2
+ import { resolveAuthRedirect } from "../utils/auth-redirect.js";
2
3
  export default defineNuxtRouteMiddleware((to) => {
3
4
  const session = useState("tco-auth-session", () => null);
4
- const routes = useRuntimeConfig().public.auth?.routes ?? {};
5
+ const authConfig = useRuntimeConfig().public.auth;
6
+ const routes = authConfig?.routes;
7
+ const logoutIntentCookieName = authConfig?.logoutIntentCookieName ?? "tco_auth_logout";
8
+ const logoutIntent = useCookie(logoutIntentCookieName);
5
9
  const authed = !!session.value;
6
10
  const isPublic = to.meta.public === true || to.meta.unauthenticatedOnly === true;
7
11
  if (authed && to.meta.unauthenticatedOnly)
8
- return navigateTo(routes.home);
9
- if (!authed && !isPublic)
10
- return navigateTo(`${routes.signIn}?redirect=${encodeURIComponent(to.fullPath)}`, { external: true });
12
+ return navigateTo(routes?.home ?? "/");
13
+ if (!authed && !isPublic) {
14
+ const redirect = resolveAuthRedirect({
15
+ loginRoute: routes?.login ?? "/auth/login",
16
+ signInRoute: routes?.signIn ?? "/auth/sign-in",
17
+ target: to.fullPath,
18
+ logoutIntent: logoutIntent.value === "1"
19
+ });
20
+ return navigateTo(redirect.to, { external: redirect.external });
21
+ }
11
22
  });
@@ -1,2 +1,2 @@
1
- declare const _default: import("#app").RouteMiddleware;
1
+ declare const _default: import("nuxt/app").RouteMiddleware;
2
2
  export default _default;
@@ -1,2 +1,2 @@
1
- declare const _default: import("#app").Plugin<Record<string, unknown>> & import("#app").ObjectPlugin<Record<string, unknown>>;
1
+ declare const _default: import("nuxt/app").Plugin<Record<string, unknown>> & import("nuxt/app").ObjectPlugin<Record<string, unknown>>;
2
2
  export default _default;
@@ -1,7 +1,7 @@
1
1
  import type { MongoAbility } from '@casl/ability';
2
- declare const _default: import("#app").Plugin<{
2
+ declare const _default: import("nuxt/app").Plugin<{
3
3
  ability: MongoAbility<import("@casl/ability").AbilityTuple, import("@casl/ability").MongoQuery>;
4
- }> & import("#app").ObjectPlugin<{
4
+ }> & import("nuxt/app").ObjectPlugin<{
5
5
  ability: MongoAbility<import("@casl/ability").AbilityTuple, import("@casl/ability").MongoQuery>;
6
6
  }>;
7
7
  export default _default;
@@ -0,0 +1,10 @@
1
+ export interface AuthRedirectOptions {
2
+ loginRoute: string;
3
+ signInRoute: string;
4
+ target: string;
5
+ logoutIntent: boolean;
6
+ }
7
+ export declare function resolveAuthRedirect(options: AuthRedirectOptions): {
8
+ external: boolean;
9
+ to: string;
10
+ };
@@ -0,0 +1,13 @@
1
+ export function resolveAuthRedirect(options) {
2
+ const redirect = encodeURIComponent(options.target);
3
+ if (options.logoutIntent) {
4
+ return {
5
+ external: false,
6
+ to: `${options.loginRoute}?loggedout=1&redirect=${redirect}`
7
+ };
8
+ }
9
+ return {
10
+ external: true,
11
+ to: `${options.signInRoute}?redirect=${redirect}`
12
+ };
13
+ }
@@ -28,6 +28,7 @@ export default defineEventHandler(async (event) => {
28
28
  refreshToken: null,
29
29
  idToken: null,
30
30
  accessExpiresAt: res.expiresAt,
31
+ authorizationRefreshedAt: Date.now(),
31
32
  isImpersonation: true,
32
33
  impersonator: { sub: s.rec.user.sub, email: s.rec.user.email, name: s.rec.user.name, picture: s.rec.user.picture },
33
34
  backupId
@@ -0,0 +1,10 @@
1
+ declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<{
2
+ items: {
3
+ sub: string;
4
+ email: string;
5
+ name: string | null;
6
+ picture: string | null;
7
+ }[];
8
+ hasMore: boolean;
9
+ }>>;
10
+ export default _default;
@@ -0,0 +1,25 @@
1
+ import { createError, defineEventHandler, getRouterParam, getValidatedQuery } from "h3";
2
+ import { z } from "zod";
3
+ import { idpFetch } from "../../../../utils/idp.js";
4
+ import { readSessionRecord } from "../../../../utils/session.js";
5
+ const querySchema = z.object({
6
+ q: z.string().trim().min(2).max(200),
7
+ limit: z.coerce.number().int().min(1).max(20).default(20),
8
+ offset: z.coerce.number().int().min(0).default(0)
9
+ });
10
+ export default defineEventHandler(async (event) => {
11
+ const session = await readSessionRecord(event);
12
+ if (!session)
13
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
14
+ const orgId = getRouterParam(event, "orgId");
15
+ if (!orgId || orgId !== session.rec.activeOrg)
16
+ throw createError({ statusCode: 403, statusMessage: "Forbidden" });
17
+ const query = await getValidatedQuery(event, querySchema.parse);
18
+ return idpFetch(
19
+ event,
20
+ session.id,
21
+ session.rec,
22
+ `/api/auth/rp/organizations/${encodeURIComponent(orgId)}/users`,
23
+ { query }
24
+ );
25
+ });
@@ -1,16 +1,26 @@
1
1
  import { createError, defineEventHandler, readValidatedBody } from "h3";
2
2
  import { z } from "zod";
3
+ import { idpFetch } from "../../../utils/idp.js";
3
4
  import { readSessionRecord, toPublicSession, writeSessionRecord } from "../../../utils/session.js";
4
- const bodySchema = z.object({ organizationId: z.string() });
5
+ const bodySchema = z.object({ organizationId: z.string().min(1) });
5
6
  export default defineEventHandler(async (event) => {
6
- const s = await readSessionRecord(event);
7
- if (!s)
7
+ const session = await readSessionRecord(event);
8
+ if (!session)
8
9
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
9
10
  const { organizationId } = await readValidatedBody(event, bodySchema.parse);
10
- const org = s.rec.organizations.find((o) => o.id === organizationId);
11
- if (!org)
12
- throw createError({ statusCode: 403, statusMessage: "Not a member of that organization" });
13
- s.rec.activeOrg = org.id;
14
- await writeSessionRecord(s.id, s.rec);
15
- return toPublicSession(s.rec);
11
+ const context = await idpFetch(
12
+ event,
13
+ session.id,
14
+ session.rec,
15
+ "/api/auth/rp/organizations/switch",
16
+ { method: "POST", body: { organizationId } }
17
+ );
18
+ session.rec.activeOrg = context.organization.id;
19
+ session.rec.organizations = context.organizations;
20
+ session.rec.abilities = context.abilities;
21
+ session.rec.systemRole = context.systemRole;
22
+ session.rec.entitlement = context.entitlement;
23
+ session.rec.authorizationRefreshedAt = Date.now();
24
+ await writeSessionRecord(session.id, session.rec);
25
+ return toPublicSession(session.rec);
16
26
  });
@@ -1,6 +1,8 @@
1
1
  import { defineEventHandler } from "h3";
2
+ import { setLogoutIntent } from "../../utils/logout-intent.js";
2
3
  import { destroySession } from "../../utils/session.js";
3
4
  export default defineEventHandler(async (event) => {
4
5
  await destroySession(event);
6
+ setLogoutIntent(event);
5
7
  return { ok: true };
6
8
  });
@@ -1,2 +1,7 @@
1
- export { getServerAuthSession } from './utils/session.js';
1
+ export { abilitySubject, canPerformStatic, createServerAbility, requireServerAbility, } from './utils/ability.js';
2
+ export type { ServerAbility } from './utils/ability.js';
3
+ export { resolveOrganizationUser } from './utils/directory.js';
4
+ export { defineAuthorizedHandler, defineSensitiveAuthorizedHandler, } from './utils/handlers.js';
5
+ export { verifyOrganizationIntegrationKey } from './utils/integration-key.js';
6
+ export { getFreshServerAuthSession, getServerAuthSession } from './utils/session.js';
2
7
  export type { ServerAuthSession } from './utils/session.js';
@@ -1 +1,13 @@
1
- export { getServerAuthSession } from "./utils/session.js";
1
+ export {
2
+ abilitySubject,
3
+ canPerformStatic,
4
+ createServerAbility,
5
+ requireServerAbility
6
+ } from "./utils/ability.js";
7
+ export { resolveOrganizationUser } from "./utils/directory.js";
8
+ export {
9
+ defineAuthorizedHandler,
10
+ defineSensitiveAuthorizedHandler
11
+ } from "./utils/handlers.js";
12
+ export { verifyOrganizationIntegrationKey } from "./utils/integration-key.js";
13
+ export { getFreshServerAuthSession, getServerAuthSession } from "./utils/session.js";
@@ -2,6 +2,7 @@ import { defineEventHandler, deleteCookie, getCookie, getQuery, sendRedirect } f
2
2
  import { useRuntimeConfig } from "nitropack/runtime";
3
3
  import { z } from "zod";
4
4
  import { UserinfoClaimsSchema } from "../../../contract";
5
+ import { clearLogoutIntent } from "../utils/logout-intent.js";
5
6
  import { callbackRedirectUri, exchangeCode, fetchUserinfo, safePath } from "../utils/oidc.js";
6
7
  import { newSessionId, setSessionCookie, writeSessionRecord } from "../utils/session.js";
7
8
  const RawUserinfoSchema = UserinfoClaimsSchema.extend({
@@ -54,10 +55,12 @@ export default defineEventHandler(async (event) => {
54
55
  refreshToken: tokens.refresh_token ?? null,
55
56
  idToken: tokens.id_token ?? null,
56
57
  accessExpiresAt: Date.now() + (tokens.expires_in ?? 3600) * 1e3,
58
+ authorizationRefreshedAt: Date.now(),
57
59
  isImpersonation: false,
58
60
  impersonator: null,
59
61
  backupId: null
60
62
  });
61
63
  await setSessionCookie(event, id);
64
+ clearLogoutIntent(event);
62
65
  return sendRedirect(event, redirectTo);
63
66
  });
@@ -1,12 +1,14 @@
1
1
  import { createError, defineEventHandler, getQuery, sendRedirect, setCookie } from "h3";
2
2
  import { useRuntimeConfig } from "nitropack/runtime";
3
3
  import { withQuery } from "ufo";
4
+ import { clearLogoutIntent } from "../utils/logout-intent.js";
4
5
  import { callbackRedirectUri, idpBaseUrl, pkceChallenge, randomString, safePath } from "../utils/oidc.js";
5
6
  const ALLOWED_PROMPTS = /* @__PURE__ */ new Set(["login", "select_account", "consent", "none"]);
6
7
  export default defineEventHandler(async (event) => {
7
8
  const { auth: runtimeConfig, public: { auth: publicRuntimeConfig } } = useRuntimeConfig();
8
9
  if (!publicRuntimeConfig.domain || !publicRuntimeConfig.clientId || !runtimeConfig.clientSecret)
9
10
  throw createError({ statusCode: 503, statusMessage: "Auth not configured" });
11
+ clearLogoutIntent(event);
10
12
  const query = getQuery(event);
11
13
  const state = randomString(32);
12
14
  const verifier = randomString(64);
@@ -1,8 +1,10 @@
1
1
  import { defineEventHandler, sendRedirect } from "h3";
2
2
  import { useRuntimeConfig } from "nitropack/runtime";
3
+ import { setLogoutIntent } from "../utils/logout-intent.js";
3
4
  import { destroySession } from "../utils/session.js";
4
5
  export default defineEventHandler(async (event) => {
5
6
  const { public: { auth: publicRuntimeConfig } } = useRuntimeConfig();
6
7
  await destroySession(event);
8
+ setLogoutIntent(event);
7
9
  return sendRedirect(event, publicRuntimeConfig.routes.home);
8
10
  });
@@ -0,0 +1,15 @@
1
+ import type { MongoAbility, Subject } from '@casl/ability';
2
+ import type { ServerAuthSession } from './session.js';
3
+ export type ServerAbility = MongoAbility<[string, Subject]>;
4
+ export declare function createServerAbility(session: ServerAuthSession, options?: {
5
+ allowSystemAdmin?: boolean;
6
+ }): ServerAbility;
7
+ export declare function abilitySubject<Name extends string, Resource extends Record<string, unknown>>(name: Name, resource: Resource): Resource & import("@casl/ability").ForcedSubject<Name>;
8
+ export declare function requireServerAbility(session: ServerAuthSession, action: string, resource: Subject, options?: {
9
+ allowSystemAdmin?: boolean;
10
+ }): void;
11
+ /**
12
+ * Static checks are safe only for unconditional rules. Conditional rules must
13
+ * be evaluated through requireServerAbility() against a loaded resource.
14
+ */
15
+ export declare function canPerformStatic(session: ServerAuthSession, action: string, subject: string): boolean;
@@ -0,0 +1,25 @@
1
+ import { createMongoAbility, subject as tagSubject } from "@casl/ability";
2
+ import { createError } from "h3";
3
+ export function createServerAbility(session, options = {}) {
4
+ const allowSystemAdmin = options.allowSystemAdmin ?? true;
5
+ const rules = allowSystemAdmin && session.systemRole === "admin" ? [{ action: "manage", subject: "all" }] : session.abilities.map((rule) => ({
6
+ action: rule.action,
7
+ subject: rule.subject,
8
+ ...rule.conditions ? { conditions: rule.conditions } : {}
9
+ }));
10
+ return createMongoAbility(rules);
11
+ }
12
+ export function abilitySubject(name, resource) {
13
+ return tagSubject(name, resource);
14
+ }
15
+ export function requireServerAbility(session, action, resource, options = {}) {
16
+ if (!createServerAbility(session, options).can(action, resource))
17
+ throw createError({ statusCode: 403, statusMessage: "Forbidden" });
18
+ }
19
+ export function canPerformStatic(session, action, subject) {
20
+ if (session.systemRole === "admin")
21
+ return true;
22
+ return session.abilities.some(
23
+ (rule) => !rule.conditions && (rule.action === action || rule.action === "manage") && (rule.subject === subject || rule.subject === "all")
24
+ );
25
+ }
@@ -0,0 +1,3 @@
1
+ import type { H3Event } from 'h3';
2
+ import type { DirectoryUser } from '../../../contract/index.js';
3
+ export declare function resolveOrganizationUser(event: H3Event, organizationId: string, userId: string): Promise<DirectoryUser>;
@@ -0,0 +1,14 @@
1
+ import { createError } from "h3";
2
+ import { idpFetch } from "./idp.js";
3
+ import { readSessionRecord } from "./session.js";
4
+ export async function resolveOrganizationUser(event, organizationId, userId) {
5
+ const session = await readSessionRecord(event);
6
+ if (!session || organizationId !== session.rec.activeOrg)
7
+ throw createError({ statusCode: 403, statusMessage: "Forbidden" });
8
+ return idpFetch(
9
+ event,
10
+ session.id,
11
+ session.rec,
12
+ `/api/auth/rp/organizations/${encodeURIComponent(organizationId)}/users/${encodeURIComponent(userId)}`
13
+ );
14
+ }
@@ -1,7 +1,10 @@
1
1
  import type { H3Event } from 'h3';
2
2
  import type { ServerAuthSession } from './session.js';
3
3
  export declare function defineAuthenticatedHandler<T>(handler: (event: H3Event, session: NonNullable<ServerAuthSession>) => Promise<T> | T): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<T>>;
4
- export declare function defineAuthorizedHandler<T>(_checks: string[], handler: (event: H3Event, ctx: {
4
+ export declare function defineAuthorizedHandler<T>(checks: string[], handler: (event: H3Event, ctx: {
5
+ session: NonNullable<ServerAuthSession>;
6
+ }) => Promise<T> | T): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<T>>;
7
+ export declare function defineSensitiveAuthorizedHandler<T>(checks: string[], handler: (event: H3Event, ctx: {
5
8
  session: NonNullable<ServerAuthSession>;
6
9
  }) => Promise<T> | T): import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<T>>;
7
10
  export declare function defineAdminHandler<T>(_checks: string[], handler: (event: H3Event, ctx: {
@@ -1,16 +1,46 @@
1
1
  import { createError, defineEventHandler } from "h3";
2
- import { getServerAuthSession } from "./session.js";
2
+ import { canPerformStatic } from "./ability.js";
3
+ import { getFreshServerAuthSession } from "./session.js";
4
+ function parseCheck(check) {
5
+ const separator = check.lastIndexOf(":");
6
+ if (separator <= 0 || separator === check.length - 1)
7
+ throw createError({ statusCode: 500, statusMessage: `Invalid ability check: ${check}` });
8
+ return { subject: check.slice(0, separator), action: check.slice(separator + 1) };
9
+ }
3
10
  export function defineAuthenticatedHandler(handler) {
4
11
  return defineEventHandler(async (event) => {
5
- const session = await getServerAuthSession(event);
12
+ const session = await getFreshServerAuthSession(event);
6
13
  if (!session)
7
14
  throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
8
15
  event.context.activeOrganizationId = session.activeOrg;
9
16
  return handler(event, session);
10
17
  });
11
18
  }
12
- export function defineAuthorizedHandler(_checks, handler) {
13
- return defineAuthenticatedHandler(async (event, session) => handler(event, { session }));
19
+ export function defineAuthorizedHandler(checks, handler) {
20
+ return defineAuthenticatedHandler(async (event, session) => {
21
+ const allowed = checks.some((check) => {
22
+ const { action, subject } = parseCheck(check);
23
+ return canPerformStatic(session, action, subject);
24
+ });
25
+ if (!allowed)
26
+ throw createError({ statusCode: 403, statusMessage: "Forbidden" });
27
+ return handler(event, { session });
28
+ });
29
+ }
30
+ export function defineSensitiveAuthorizedHandler(checks, handler) {
31
+ return defineEventHandler(async (event) => {
32
+ const session = await getFreshServerAuthSession(event, { force: true });
33
+ if (!session)
34
+ throw createError({ statusCode: 401, statusMessage: "Unauthorized" });
35
+ event.context.activeOrganizationId = session.activeOrg;
36
+ const allowed = checks.some((check) => {
37
+ const { action, subject } = parseCheck(check);
38
+ return canPerformStatic(session, action, subject);
39
+ });
40
+ if (!allowed)
41
+ throw createError({ statusCode: 403, statusMessage: "Forbidden" });
42
+ return handler(event, { session });
43
+ });
14
44
  }
15
45
  export function defineAdminHandler(_checks, handler) {
16
46
  return defineAuthenticatedHandler(async (event, session) => {
@@ -1,5 +1,7 @@
1
1
  import type { H3Event } from 'h3';
2
+ import type { OrganizationContext } from '../../../contract/index.js';
2
3
  import type { SessionRecord } from './session.js';
4
+ export declare function refreshAuthorizationContext(event: H3Event, id: string, rec: SessionRecord): Promise<OrganizationContext>;
3
5
  /**
4
6
  * Call an IdP path with the session's bearer token. Refreshes once on expiry/401.
5
7
  * Persists the rotated record. Returns parsed JSON.
@@ -4,6 +4,17 @@ import { $fetch } from "ofetch";
4
4
  import { idpBaseUrl } from "./oidc.js";
5
5
  import { readSessionRecordById, writeSessionRecord } from "./session.js";
6
6
  const SKEW_MS = 6e4;
7
+ export async function refreshAuthorizationContext(event, id, rec) {
8
+ if (!rec.activeOrg)
9
+ throw createError({ statusCode: 403, statusMessage: "No active organization" });
10
+ return idpFetch(
11
+ event,
12
+ id,
13
+ rec,
14
+ "/api/auth/rp/organizations/context",
15
+ { method: "POST", body: { organizationId: rec.activeOrg } }
16
+ );
17
+ }
7
18
  async function refresh(rec) {
8
19
  if (rec.isImpersonation || !rec.refreshToken)
9
20
  return false;
@@ -0,0 +1,13 @@
1
+ import type { H3Event } from 'h3';
2
+ export declare function verifyOrganizationIntegrationKey(_event: H3Event, key: string): Promise<{
3
+ keyId: string;
4
+ organizationId: string;
5
+ clientId: string;
6
+ audience: "vault";
7
+ resourceType: "environment";
8
+ resourceId: string;
9
+ permissions: "secrets:read"[];
10
+ name: string;
11
+ prefix: string;
12
+ expiresAt: number | null;
13
+ }>;
@@ -0,0 +1,27 @@
1
+ import { createError } from "h3";
2
+ import { useRuntimeConfig } from "nitropack/runtime";
3
+ import { $fetch } from "ofetch";
4
+ import { IntegrationKeyContextSchema } from "../../../contract";
5
+ import { idpBaseUrl } from "./oidc.js";
6
+ export async function verifyOrganizationIntegrationKey(_event, key) {
7
+ const config = useRuntimeConfig();
8
+ try {
9
+ const raw = await $fetch(`${idpBaseUrl()}/api/auth/rp/integration-keys/verify`, {
10
+ method: "POST",
11
+ headers: {
12
+ Authorization: `Basic ${btoa(`${config.public.auth.clientId}:${config.auth.clientSecret}`)}`
13
+ },
14
+ body: { key }
15
+ });
16
+ return IntegrationKeyContextSchema.parse(raw);
17
+ } catch (error) {
18
+ const status = error.response?.status;
19
+ if (status === 401 || status === 403 || status === 429) {
20
+ throw createError({
21
+ statusCode: status,
22
+ statusMessage: status === 429 ? "Rate limited" : "Invalid API token"
23
+ });
24
+ }
25
+ throw createError({ statusCode: 503, statusMessage: "Authorization service unavailable" });
26
+ }
27
+ }
@@ -0,0 +1,3 @@
1
+ import type { H3Event } from 'h3';
2
+ export declare function setLogoutIntent(event: H3Event): void;
3
+ export declare function clearLogoutIntent(event: H3Event): void;
@@ -0,0 +1,17 @@
1
+ import { deleteCookie, setCookie } from "h3";
2
+ import { useRuntimeConfig } from "nitropack/runtime";
3
+ const LOGOUT_INTENT_MAX_AGE = 60 * 60 * 24 * 7;
4
+ export function setLogoutIntent(event) {
5
+ const { auth: runtimeConfig } = useRuntimeConfig();
6
+ setCookie(event, runtimeConfig.logoutIntentCookieName, "1", {
7
+ httpOnly: false,
8
+ secure: !import.meta.dev,
9
+ sameSite: "lax",
10
+ path: "/",
11
+ maxAge: LOGOUT_INTENT_MAX_AGE
12
+ });
13
+ }
14
+ export function clearLogoutIntent(event) {
15
+ const { auth: runtimeConfig } = useRuntimeConfig();
16
+ deleteCookie(event, runtimeConfig.logoutIntentCookieName, { path: "/" });
17
+ }
@@ -5,6 +5,7 @@ declare module 'nitropack' {
5
5
  clientSecret: string;
6
6
  sessionStorageBase: string;
7
7
  sessionCookieName: string;
8
+ logoutIntentCookieName: string;
8
9
  };
9
10
  }
10
11
  interface NitroRuntimePublicConfig {
@@ -12,12 +13,14 @@ declare module 'nitropack' {
12
13
  domain: string;
13
14
  clientId: string;
14
15
  routes: {
16
+ login: string;
15
17
  signIn: string;
16
18
  callback: string;
17
19
  signOut: string;
18
20
  home: string;
19
21
  error: string;
20
22
  };
23
+ logoutIntentCookieName: string;
21
24
  scopes: string[];
22
25
  };
23
26
  }
@@ -1,5 +1,6 @@
1
1
  import type { H3Event } from 'h3';
2
- import type { AbilityRule, PublicSession } from '../../../contract/index.js';
2
+ import type { AbilityRule, PublicSession, ServerAuthSession as ServerAuthSessionContract } from '../../../contract/index.js';
3
+ export declare const AUTHORIZATION_TTL_MS = 120000;
3
4
  export interface SessionRecord {
4
5
  sub: string;
5
6
  user: {
@@ -17,6 +18,7 @@ export interface SessionRecord {
17
18
  refreshToken: string | null;
18
19
  idToken: string | null;
19
20
  accessExpiresAt: number;
21
+ authorizationRefreshedAt: number;
20
22
  isImpersonation: boolean;
21
23
  impersonator: {
22
24
  sub: string;
@@ -35,41 +37,13 @@ export declare function readSessionRecord(event: H3Event): Promise<{
35
37
  export declare function setSessionCookie(event: H3Event, id: string): Promise<void>;
36
38
  export declare function destroySession(event: H3Event): Promise<SessionRecord | null>;
37
39
  export declare function readSessionRecordById(id: string): Promise<SessionRecord | null>;
38
- /** Server-safe session for domain routes — no tokens, no internal storage fields. */
39
- export declare function getServerAuthSession(event: H3Event): Promise<{
40
- sub: string;
41
- email: string;
42
- name: string | null;
43
- picture: string | null;
44
- systemRole: string | null;
45
- abilities: {
46
- action: string;
47
- subject: string;
48
- conditions?: Record<string, string> | undefined;
49
- }[];
50
- organizations: {
51
- id: string;
52
- slug: string;
53
- name: string;
54
- role: string;
55
- personal: boolean;
56
- }[];
57
- activeOrg: string | null;
58
- entitlement: {
59
- product: string;
60
- plan: string;
61
- status: string;
62
- active: boolean;
63
- currentPeriodEnd: number | null;
64
- } | null;
65
- isImpersonation: boolean;
66
- impersonator: {
67
- sub: string;
68
- email: string;
69
- name: string | null;
70
- picture: string | null;
71
- } | null;
72
- } | null>;
73
- export type ServerAuthSession = NonNullable<Awaited<ReturnType<typeof getServerAuthSession>>>;
40
+ export type ServerAuthSession = ServerAuthSessionContract;
41
+ /** Server-safe session projection no tokens or private refresh metadata. */
42
+ export declare function toServerAuthSession(rec: SessionRecord): ServerAuthSession;
43
+ /** Server-safe session for callers that explicitly do not need a live refresh. */
44
+ export declare function getServerAuthSession(event: H3Event): Promise<ServerAuthSession | null>;
45
+ export declare function getFreshServerAuthSession(event: H3Event, options?: {
46
+ force?: boolean;
47
+ }): Promise<ServerAuthSession | null>;
74
48
  /** Browser-safe projection — tokens are excluded. */
75
49
  export declare function toPublicSession(rec: SessionRecord): PublicSession;
@@ -1,5 +1,7 @@
1
- import { deleteCookie, getCookie, setCookie } from "h3";
1
+ import { createError, deleteCookie, getCookie, setCookie } from "h3";
2
2
  import { useRuntimeConfig, useStorage } from "nitropack/runtime";
3
+ import { refreshAuthorizationContext } from "./idp.js";
4
+ export const AUTHORIZATION_TTL_MS = 12e4;
3
5
  function key(id) {
4
6
  return `session:${id}`;
5
7
  }
@@ -44,24 +46,55 @@ export async function readSessionRecordById(id) {
44
46
  const { auth: runtimeConfig } = useRuntimeConfig();
45
47
  return useStorage(runtimeConfig.sessionStorageBase).getItem(key(id));
46
48
  }
47
- export async function getServerAuthSession(event) {
48
- const s = await readSessionRecord(event);
49
- if (!s)
50
- return null;
49
+ export function toServerAuthSession(rec) {
51
50
  return {
52
- sub: s.rec.sub,
53
- email: s.rec.user.email,
54
- name: s.rec.user.name,
55
- picture: s.rec.user.picture,
56
- systemRole: s.rec.systemRole,
57
- abilities: s.rec.abilities,
58
- organizations: s.rec.organizations,
59
- activeOrg: s.rec.activeOrg,
60
- entitlement: s.rec.entitlement,
61
- isImpersonation: s.rec.isImpersonation,
62
- impersonator: s.rec.impersonator
51
+ sub: rec.sub,
52
+ email: rec.user.email,
53
+ name: rec.user.name,
54
+ picture: rec.user.picture,
55
+ systemRole: rec.systemRole,
56
+ abilities: rec.abilities,
57
+ organizations: rec.organizations,
58
+ activeOrg: rec.activeOrg,
59
+ entitlement: rec.entitlement,
60
+ isImpersonation: rec.isImpersonation,
61
+ impersonator: rec.impersonator
63
62
  };
64
63
  }
64
+ export async function getServerAuthSession(event) {
65
+ const session = await readSessionRecord(event);
66
+ return session ? toServerAuthSession(session.rec) : null;
67
+ }
68
+ export async function getFreshServerAuthSession(event, options = {}) {
69
+ const session = await readSessionRecord(event);
70
+ if (!session)
71
+ return null;
72
+ if (!options.force && Date.now() - session.rec.authorizationRefreshedAt <= AUTHORIZATION_TTL_MS)
73
+ return toServerAuthSession(session.rec);
74
+ try {
75
+ const context = await refreshAuthorizationContext(event, session.id, session.rec);
76
+ session.rec.activeOrg = context.organization.id;
77
+ session.rec.organizations = context.organizations;
78
+ session.rec.abilities = context.abilities;
79
+ session.rec.systemRole = context.systemRole;
80
+ session.rec.entitlement = context.entitlement;
81
+ session.rec.authorizationRefreshedAt = Date.now();
82
+ await writeSessionRecord(session.id, session.rec);
83
+ return toServerAuthSession(session.rec);
84
+ } catch (error) {
85
+ const status = error.statusCode ?? error.response?.status;
86
+ if (status === 401 || status === 403) {
87
+ session.rec.abilities = [];
88
+ session.rec.organizations = [];
89
+ session.rec.activeOrg = null;
90
+ session.rec.systemRole = null;
91
+ session.rec.authorizationRefreshedAt = Date.now();
92
+ await writeSessionRecord(session.id, session.rec);
93
+ throw createError({ statusCode: 403, statusMessage: "Authorization changed; sign in again" });
94
+ }
95
+ throw createError({ statusCode: 503, statusMessage: "Authorization service unavailable" });
96
+ }
97
+ }
65
98
  export function toPublicSession(rec) {
66
99
  return {
67
100
  user: rec.user,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@thecodeorigin/auth",
3
3
  "type": "module",
4
- "version": "0.0.7",
4
+ "version": "0.0.9",
5
5
  "private": false,
6
6
  "description": "THECODEORIGIN Authentication Portal client module",
7
7
  "repository": "https://github.com/thecodeorigin/auth",
@@ -39,32 +39,8 @@
39
39
  "workspaces": [
40
40
  "playground"
41
41
  ],
42
- "dependencies": {
43
- "@casl/ability": "^6.8.1",
44
- "@casl/vue": "^2.2.6",
45
- "@nuxt/kit": "^4.4.8",
46
- "defu": "^6.1.4",
47
- "h3": "^1.14.0",
48
- "ofetch": "^1.4.1",
49
- "ufo": "^1.5.4",
50
- "zod": "^4.3.6"
51
- },
52
- "devDependencies": {
53
- "@nuxt/devtools": "^3.2.4",
54
- "@nuxt/eslint-config": "^1.15.2",
55
- "@nuxt/module-builder": "^1.0.2",
56
- "@nuxt/schema": "^4.4.8",
57
- "@nuxt/test-utils": "^4.0.3",
58
- "@types/node": "latest",
59
- "changelogen": "^0.6.2",
60
- "eslint": "^10.4.1",
61
- "nuxt": "^4.4.8",
62
- "typescript": "~6.0.3",
63
- "unbuild": "^3.6.1",
64
- "vitest": "^4.1.8",
65
- "vue-tsc": "^3.3.3"
66
- },
67
42
  "scripts": {
43
+ "prepack": "nuxt-module-build build",
68
44
  "dev": "npm run dev:prepare && nuxt dev playground",
69
45
  "dev:build": "nuxt build playground",
70
46
  "dev:prepare": "nuxt-module-build build --stub && nuxt-module-build prepare && nuxt prepare playground",
@@ -72,5 +48,31 @@
72
48
  "test": "vitest run",
73
49
  "test:watch": "vitest watch",
74
50
  "test:types": "vue-tsc --noEmit && cd playground && vue-tsc --noEmit"
51
+ },
52
+ "dependencies": {
53
+ "@casl/ability": "catalog:",
54
+ "@casl/vue": "catalog:",
55
+ "@nuxt/kit": "catalog:",
56
+ "defu": "catalog:",
57
+ "h3": "catalog:",
58
+ "ofetch": "catalog:",
59
+ "ufo": "catalog:",
60
+ "zod": "catalog:"
61
+ },
62
+ "devDependencies": {
63
+ "@antfu/eslint-config": "catalog:",
64
+ "@nuxt/devtools": "catalog:",
65
+ "@nuxt/eslint-config": "catalog:",
66
+ "@nuxt/module-builder": "catalog:",
67
+ "@nuxt/schema": "catalog:",
68
+ "@nuxt/test-utils": "catalog:",
69
+ "@types/node": "catalog:",
70
+ "changelogen": "catalog:",
71
+ "eslint": "catalog:",
72
+ "nuxt": "catalog:",
73
+ "typescript": "catalog:",
74
+ "unbuild": "catalog:",
75
+ "vitest": "catalog:",
76
+ "vue-tsc": "catalog:"
75
77
  }
76
- }
78
+ }