@zephkelly/nuxt-authkit 0.0.1

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 (75) hide show
  1. package/README.md +170 -0
  2. package/dist/module.d.mts +6 -0
  3. package/dist/module.json +9 -0
  4. package/dist/module.mjs +80 -0
  5. package/dist/runtime/composables/useAuthkitToken.d.ts +25 -0
  6. package/dist/runtime/composables/useAuthkitToken.js +31 -0
  7. package/dist/runtime/helpers/request-middleware.d.ts +22 -0
  8. package/dist/runtime/helpers/request-middleware.js +82 -0
  9. package/dist/runtime/index.d.ts +8 -0
  10. package/dist/runtime/index.js +2 -0
  11. package/dist/runtime/plugins/auth-fetch.client.d.ts +13 -0
  12. package/dist/runtime/plugins/auth-fetch.client.js +91 -0
  13. package/dist/runtime/plugins/internal/request-target.d.ts +16 -0
  14. package/dist/runtime/plugins/internal/request-target.js +17 -0
  15. package/dist/runtime/roles/has-role.d.ts +21 -0
  16. package/dist/runtime/roles/has-role.js +51 -0
  17. package/dist/runtime/server/api/_auth/refresh-handler.d.ts +6 -0
  18. package/dist/runtime/server/api/_auth/refresh-handler.js +44 -0
  19. package/dist/runtime/server/api/_auth/refresh.post.d.ts +6 -0
  20. package/dist/runtime/server/api/_auth/refresh.post.js +5 -0
  21. package/dist/runtime/server/tsconfig.json +3 -0
  22. package/dist/runtime/service/index.d.ts +119 -0
  23. package/dist/runtime/service/index.js +170 -0
  24. package/dist/runtime/service/new-index.d.ts +24 -0
  25. package/dist/runtime/service/new-index.js +0 -0
  26. package/dist/runtime/service/types/async-service.d.ts +59 -0
  27. package/dist/runtime/service/types/async-service.js +0 -0
  28. package/dist/runtime/service/types/sync-service.d.ts +28 -0
  29. package/dist/runtime/service/types/sync-service.js +0 -0
  30. package/dist/runtime/service/utils/getJwtAsyncService.d.ts +11 -0
  31. package/dist/runtime/service/utils/getJwtAsyncService.js +16 -0
  32. package/dist/runtime/storage/nitro.d.ts +61 -0
  33. package/dist/runtime/storage/nitro.js +124 -0
  34. package/dist/runtime/storage/types.d.ts +27 -0
  35. package/dist/runtime/storage/types.js +0 -0
  36. package/dist/runtime/strategy/base.d.ts +40 -0
  37. package/dist/runtime/strategy/base.js +88 -0
  38. package/dist/runtime/strategy/jwt/callback.d.ts +60 -0
  39. package/dist/runtime/strategy/jwt/callback.js +0 -0
  40. package/dist/runtime/strategy/jwt/index.d.ts +137 -0
  41. package/dist/runtime/strategy/jwt/index.js +748 -0
  42. package/dist/runtime/strategy/jwt/jwt-crypto.d.ts +122 -0
  43. package/dist/runtime/strategy/jwt/jwt-crypto.js +298 -0
  44. package/dist/runtime/strategy/jwt/types.d.ts +68 -0
  45. package/dist/runtime/strategy/jwt/types.js +0 -0
  46. package/dist/runtime/strategy/types.d.ts +68 -0
  47. package/dist/runtime/strategy/types.js +0 -0
  48. package/dist/runtime/types/2fac.d.ts +32 -0
  49. package/dist/runtime/types/2fac.js +0 -0
  50. package/dist/runtime/types/authkit-types.d.ts +6 -0
  51. package/dist/runtime/types/authkit-types.js +0 -0
  52. package/dist/runtime/types/callbacks.d.ts +133 -0
  53. package/dist/runtime/types/callbacks.js +0 -0
  54. package/dist/runtime/types/cookie.d.ts +47 -0
  55. package/dist/runtime/types/cookie.js +0 -0
  56. package/dist/runtime/types/expand.d.ts +3 -0
  57. package/dist/runtime/types/expand.js +0 -0
  58. package/dist/runtime/types/index.d.ts +52 -0
  59. package/dist/runtime/types/index.js +0 -0
  60. package/dist/runtime/types/module-options.d.ts +33 -0
  61. package/dist/runtime/types/module-options.js +0 -0
  62. package/dist/runtime/types/runtime-config.d.ts +2 -0
  63. package/dist/runtime/types/runtime-config.js +47 -0
  64. package/dist/runtime/types/token.d.ts +42 -0
  65. package/dist/runtime/types/token.js +12 -0
  66. package/dist/runtime/utils/context-helpers.d.ts +4 -0
  67. package/dist/runtime/utils/context-helpers.js +10 -0
  68. package/dist/runtime/utils/get-module-options.d.ts +2 -0
  69. package/dist/runtime/utils/get-module-options.js +18 -0
  70. package/dist/runtime/utils/password.d.ts +23 -0
  71. package/dist/runtime/utils/password.js +18 -0
  72. package/dist/runtime/utils/uuid.d.ts +187 -0
  73. package/dist/runtime/utils/uuid.js +345 -0
  74. package/dist/types.d.mts +7 -0
  75. package/package.json +65 -0
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Storage interface for tokens/sessions
3
+ * Implementations can use cookies, localStorage, memory, etc.
4
+ */
5
+ export interface AuthStorage {
6
+ get(key: string): Promise<string | null> | string | null;
7
+ set(key: string, value: string, options?: StorageOptions): Promise<void> | void;
8
+ remove(key: string): Promise<void> | void;
9
+ clear(): Promise<void> | void;
10
+ }
11
+ export interface StorageOptions {
12
+ maxAge?: number;
13
+ httpOnly?: boolean;
14
+ secure?: boolean;
15
+ sameSite?: 'strict' | 'lax' | 'none';
16
+ domain?: string;
17
+ path?: string;
18
+ }
19
+ /**
20
+ * Storage adapter interface compatible with Nitro's useStorage()
21
+ */
22
+ export interface StorageAdapter {
23
+ getItem: <T = any>(key: string) => Promise<T | null>;
24
+ setItem: (key: string, value: any) => Promise<void>;
25
+ removeItem: (key: string) => Promise<void>;
26
+ getKeys: (prefix?: string) => Promise<string[]>;
27
+ }
File without changes
@@ -0,0 +1,40 @@
1
+ import type { H3Event } from "h3";
2
+ import type { IAuthStrategy, StrategyConfig } from "./types.js";
3
+ import type { AuthResult, VerifyAuthResult, AuthError } from "../types/index.js";
4
+ import type { AuthCallbacks } from "../types/callbacks.js";
5
+ import type { AuthkitCredentials, AuthkitUser } from './../types/authkit-types.js';
6
+ /**
7
+ * Abstract base class for authentication strategies
8
+ * Provides common functionality and enforces consistent interface
9
+ */
10
+ export declare abstract class BaseStrategy<TCredentials = AuthkitCredentials> implements IAuthStrategy<TCredentials> {
11
+ readonly name: string;
12
+ readonly config: StrategyConfig;
13
+ protected callbacks: AuthCallbacks;
14
+ constructor(config: StrategyConfig, callbacks: AuthCallbacks);
15
+ protected validateConfig(): void;
16
+ abstract authenticate(credentials: TCredentials, event: H3Event, options?: {
17
+ accessExpiresIn?: number;
18
+ refreshExpiresIn?: number;
19
+ }): Promise<AuthResult>;
20
+ abstract verify(event: H3Event, options?: any): VerifyAuthResult;
21
+ abstract refresh(event: H3Event, options?: any): Promise<AuthResult>;
22
+ abstract revoke(event: H3Event): Promise<void>;
23
+ protected createError(code: AuthError['code'], message: string, details?: unknown, recoverable?: boolean): Error;
24
+ protected extractAuthError(error: unknown): AuthError;
25
+ protected notifyAuthenticated(result: AuthResult, event: H3Event): Promise<void>;
26
+ protected notifyAuthenticationFailed(error: AuthError, event: H3Event): Promise<void>;
27
+ protected notifyLogout(userId: string, event: H3Event): Promise<void>;
28
+ protected handleError(error: unknown, event: H3Event): Promise<AuthResult>;
29
+ protected getStructuredAuthenticateResult(authenticateResult: string | {
30
+ id: string;
31
+ roles?: string[];
32
+ user?: any;
33
+ claims?: Record<string, unknown>;
34
+ } | null): {
35
+ id: string;
36
+ roles?: string[];
37
+ user?: AuthkitUser;
38
+ claims?: Record<string, unknown>;
39
+ } | null;
40
+ }
@@ -0,0 +1,88 @@
1
+ export class BaseStrategy {
2
+ name;
3
+ config;
4
+ callbacks;
5
+ constructor(config, callbacks) {
6
+ this.name = config.name;
7
+ this.config = config;
8
+ this.callbacks = callbacks;
9
+ this.validateConfig();
10
+ }
11
+ validateConfig() {
12
+ if (!this.config.name) {
13
+ throw new Error("Strategy name is required");
14
+ }
15
+ }
16
+ // ========== Helper Methods ==========
17
+ createError(code, message, details, recoverable = true) {
18
+ const error = new Error(message);
19
+ error.authError = {
20
+ code,
21
+ message,
22
+ details,
23
+ recoverable
24
+ };
25
+ return error;
26
+ }
27
+ extractAuthError(error) {
28
+ if (error && typeof error === "object" && "authError" in error) {
29
+ return error.authError;
30
+ }
31
+ return {
32
+ code: "UNKNOWN_ERROR",
33
+ message: error instanceof Error ? error.message : "An error occurred",
34
+ recoverable: true
35
+ };
36
+ }
37
+ async notifyAuthenticated(result, event) {
38
+ if (this.callbacks.onAuthenticated && result.tokens) {
39
+ try {
40
+ await this.callbacks.onAuthenticated(
41
+ result.tokens,
42
+ event
43
+ );
44
+ } catch (error) {
45
+ console.error("Error in onAuthenticated callback:", error);
46
+ }
47
+ }
48
+ }
49
+ async notifyAuthenticationFailed(error, event) {
50
+ if (this.callbacks.onAuthenticationFailed) {
51
+ try {
52
+ await this.callbacks.onAuthenticationFailed(error.code, event);
53
+ } catch (err) {
54
+ console.error("Error in onAuthenticationFailed callback:", err);
55
+ }
56
+ }
57
+ }
58
+ async notifyLogout(userId, event) {
59
+ if (this.callbacks.onLogout) {
60
+ try {
61
+ await this.callbacks.onLogout(userId, event);
62
+ } catch (error) {
63
+ console.error("Error in onLogout callback:", error);
64
+ }
65
+ }
66
+ }
67
+ async handleError(error, event) {
68
+ const authError = this.extractAuthError(error);
69
+ await this.notifyAuthenticationFailed(authError, event);
70
+ return {
71
+ success: false,
72
+ error: authError
73
+ };
74
+ }
75
+ getStructuredAuthenticateResult(authenticateResult) {
76
+ if (typeof authenticateResult === "string") {
77
+ return { id: authenticateResult };
78
+ } else if (authenticateResult && typeof authenticateResult === "object" && "id" in authenticateResult) {
79
+ return {
80
+ id: authenticateResult.id,
81
+ roles: authenticateResult.roles,
82
+ user: authenticateResult.user,
83
+ claims: authenticateResult.claims
84
+ };
85
+ }
86
+ return null;
87
+ }
88
+ }
@@ -0,0 +1,60 @@
1
+ import type { H3Event } from "h3";
2
+ import type { AuthErrorCode } from '../../types/index.js';
3
+ import type { TokenSet } from '../../types/token.js';
4
+ import type { AuthCallbacksGeneral, RefreshTokenLookupResult } from '../../types/callbacks.js';
5
+ import type { AuthkitCredentials, AuthkitUser } from './../../types/authkit-types.js';
6
+ import type { Expand } from '../../types/expand.js';
7
+ import type { JWTServerRevokeOptions } from './types.js';
8
+ export interface JWTAuthCallbacks extends AuthCallbacksGeneral {
9
+ /**
10
+ * Verify user credentials.
11
+ * Called during initial authentication.
12
+ *
13
+ * Return the user id, or an object carrying `roles` and any custom `claims`
14
+ * to embed in the access token.
15
+ */
16
+ onAuthenticate(credentials: Expand<AuthkitCredentials>, event: H3Event): Promise<string | {
17
+ id: string;
18
+ roles?: string[];
19
+ user?: AuthkitUser;
20
+ claims?: Record<string, unknown>;
21
+ } | null>;
22
+ onStoreRefreshToken(refreshToken: string, identifier: string, event: H3Event): Promise<void>;
23
+ onGetRefreshToken(refreshToken: string, identifier: string, event: H3Event): Promise<RefreshTokenLookupResult>;
24
+ onRemoveRefreshToken?(refreshToken: string, identifier: string, event: H3Event): Promise<void>;
25
+ onGetRoles?(userId: string, event: H3Event): Promise<string[] | undefined> | string[] | undefined;
26
+ onRevokeTokenFamily?(family: string, userId: string, event: H3Event): Promise<void>;
27
+ onRevokeServer?(options: Expand<JWTServerRevokeOptions>, event?: H3Event): Promise<void>;
28
+ /**
29
+ * Resolve the refresh cookie's `Domain` attribute per request.
30
+ *
31
+ * A single API can legitimately serve clients on more than one registrable
32
+ * domain, and the refresh cookie has to be set on the one the request
33
+ * actually came from — a static `cookie.domain` can only ever satisfy one of
34
+ * them, and the others silently get no cookie.
35
+ *
36
+ * Return a falsy value to fall back to the configured `cookie.domain`.
37
+ *
38
+ * @example
39
+ * ```ts
40
+ * onResolveCookieDomain(event) {
41
+ * const origin = getHeader(event, 'origin') ?? '';
42
+ * return origin.endsWith('.example.dev') ? '.example.dev' : '.example.com';
43
+ * }
44
+ * ```
45
+ */
46
+ onResolveCookieDomain?(event: H3Event): string | undefined;
47
+ onAuthenticated?(tokens: TokenSet, event: H3Event): Promise<void> | void;
48
+ /**
49
+ * Called when authentication fails
50
+ */
51
+ onAuthenticationFailed?(reason: AuthErrorCode, event: H3Event): Promise<void> | void;
52
+ /**
53
+ * Called when tokens are refreshed
54
+ */
55
+ onTokenRefreshed?(tokens: TokenSet, event: H3Event): Promise<void> | void;
56
+ /**
57
+ * Called when user logs out
58
+ */
59
+ onLogout?(userId: string, event: H3Event): Promise<void> | void;
60
+ }
File without changes
@@ -0,0 +1,137 @@
1
+ import type { AuthResult, VerifyAuthResult } from '../../types/index.js';
2
+ import type { JWTAuthCallbacks } from './callback.js';
3
+ import { BaseStrategy } from '../base.js';
4
+ import type { JWTVerifyOptions, JWTRefreshOptions, JWTServerRevokeOptions } from './types.js';
5
+ import type { AuthkitCredentials } from './../../types/authkit-types.js';
6
+ import { type H3Event } from 'h3';
7
+ import type { Expand } from '../../types/expand.js';
8
+ /**
9
+ * JWT Authentication Strategy
10
+ * Implements access/refresh token pattern with rotating refresh tokens
11
+ */
12
+ export declare class JWTStrategy extends BaseStrategy {
13
+ private readonly jwtCrypto;
14
+ private readonly jwtConfig;
15
+ protected callbacks: JWTAuthCallbacks;
16
+ constructor(callbacks: Expand<JWTAuthCallbacks>);
17
+ authenticate(credentials: AuthkitCredentials, event: H3Event, options?: {
18
+ accessExpiresIn?: number;
19
+ refreshExpiresIn?: number;
20
+ }): Promise<AuthResult>;
21
+ verify(event: H3Event, options?: JWTVerifyOptions): VerifyAuthResult;
22
+ /**
23
+ * Verify a bare access token, with no request context.
24
+ *
25
+ * `verify()` resolves the token from the ambient H3 event, which does not
26
+ * exist for a WebSocket peer, a queue worker, or a signed asset URL carrying
27
+ * its own token. Those callers have a token and nothing else, and this is the
28
+ * entry point for them.
29
+ *
30
+ * Claim validation is identical to `verify()` — in particular `typ` must be
31
+ * `'access'`, so a refresh token cannot be replayed here to open a socket.
32
+ */
33
+ verifyToken(accessToken: string): VerifyAuthResult;
34
+ /**
35
+ * Mint a token pair for a user directly, without presenting credentials.
36
+ *
37
+ * `authenticate()` is the credential path and always runs `onAuthenticate`.
38
+ * This is the path for the flows where the app has *already* established who
39
+ * the user is by some other means — an OAuth callback, an accepted invite, an
40
+ * admin impersonating a user, a device-linking handshake — and needs to hand
41
+ * out a session for a subject it chose itself.
42
+ *
43
+ * It is a session-minting primitive with no authentication of its own: the
44
+ * caller is asserting the identity. Never reach it from an unauthenticated
45
+ * route without first proving the subject some other way.
46
+ *
47
+ * Starts a fresh rotation family, so it is a real new session rather than a
48
+ * graft onto an existing one.
49
+ */
50
+ issueTokens(userId: string, event: H3Event, options?: {
51
+ roles?: string[];
52
+ claims?: Record<string, unknown>;
53
+ accessExpiresIn?: number;
54
+ refreshExpiresIn?: number;
55
+ }): Promise<AuthResult>;
56
+ refresh(event: H3Event, options?: JWTRefreshOptions): Promise<AuthResult>;
57
+ revoke(event: H3Event): Promise<void>;
58
+ /**
59
+ * Server-initiated revocation (admin force-logout, scheduled cleanup...).
60
+ *
61
+ * Throws when no `onRevokeServer` callback is configured. Only the app knows
62
+ * where its tokens live, so there is nothing this can do on its own — and
63
+ * reporting success while doing nothing is worse than failing.
64
+ */
65
+ revokeServer(options: JWTServerRevokeOptions, event?: H3Event): Promise<void>;
66
+ /**
67
+ * Decide whether a payload is an access or a refresh token.
68
+ *
69
+ * `typ` is mandatory: a token that does not say what it is, is not usable as
70
+ * anything. Both kinds are signed with the same key, so this claim is the only
71
+ * thing preventing a refresh token from being replayed as a 7-day Bearer
72
+ * session. There is deliberately no fallback for tokens minted before the claim
73
+ * existed — they are rejected, which logs their holders out once.
74
+ */
75
+ private resolveTokenUse;
76
+ private validateClaims;
77
+ /** `aud` is `string | string[]` on both sides; they match if they intersect. */
78
+ private audienceMatches;
79
+ private shouldRotate;
80
+ /**
81
+ * Accepts the legacy `string | null` return as well as the richer
82
+ * {@link RefreshTokenLookup}, so existing callbacks keep working unchanged.
83
+ */
84
+ private normaliseRefreshLookup;
85
+ private resolveRolesForRefresh;
86
+ private removeStoredRefreshToken;
87
+ private constantTimeEquals;
88
+ private generateTokens;
89
+ /**
90
+ * Carry an existing token's custom claims through rotation, dropping the
91
+ * reserved ones (which are always recomputed).
92
+ */
93
+ private extractCustomClaims;
94
+ private stripReservedClaims;
95
+ /**
96
+ * Static `jwt.claims` that collide with a computed claim are a configuration
97
+ * error, not something to silently drop: a stray `exp` there would otherwise
98
+ * have minted never-expiring tokens app-wide.
99
+ */
100
+ private assertNoReservedClaims;
101
+ private warnOnWeakConfiguration;
102
+ private encodeToken;
103
+ /**
104
+ * Verify and decode a token.
105
+ *
106
+ * Returns `null` for any cryptographically-invalid token — malformed, bad
107
+ * signature, wrong algorithm. The distinction is logged server-side but never
108
+ * surfaced to the caller, so it cannot become an error oracle, and callers get
109
+ * a single unambiguous "invalid" they can fail closed on.
110
+ */
111
+ private decodeToken;
112
+ private invalidToken;
113
+ private invalidRefresh;
114
+ private shouldRefreshToken;
115
+ private notifyTokenRefreshed;
116
+ private readRefreshHttpOnlyCookie;
117
+ private revokeRefreshHttpOnlyCookie;
118
+ private issueRefreshHttpOnlyCookie;
119
+ /**
120
+ * `httpOnly` is forced on rather than merged in. These helpers are named for a
121
+ * guarantee, and a refresh token readable from JavaScript is exactly the
122
+ * credential this design exists to keep out of reach.
123
+ *
124
+ * The domain is resolved per request when `onResolveCookieDomain` is supplied:
125
+ * one API can serve clients on several registrable domains, and a static
126
+ * `cookie.domain` can only ever satisfy one of them. Deletion resolves the
127
+ * domain the same way — a cookie set on one domain cannot be cleared by a
128
+ * Set-Cookie scoped to another.
129
+ */
130
+ private resolveRefreshCookieOptions;
131
+ /**
132
+ * RFC 7235 makes the auth-scheme case-insensitive, and an anchored match keeps
133
+ * a header without the scheme from being returned verbatim as a "token".
134
+ */
135
+ private getAuthBearerToken;
136
+ }
137
+ export declare function createJWTStrategy(callbacks: Expand<JWTAuthCallbacks>): JWTStrategy;