@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.
- package/README.md +170 -0
- package/dist/module.d.mts +6 -0
- package/dist/module.json +9 -0
- package/dist/module.mjs +80 -0
- package/dist/runtime/composables/useAuthkitToken.d.ts +25 -0
- package/dist/runtime/composables/useAuthkitToken.js +31 -0
- package/dist/runtime/helpers/request-middleware.d.ts +22 -0
- package/dist/runtime/helpers/request-middleware.js +82 -0
- package/dist/runtime/index.d.ts +8 -0
- package/dist/runtime/index.js +2 -0
- package/dist/runtime/plugins/auth-fetch.client.d.ts +13 -0
- package/dist/runtime/plugins/auth-fetch.client.js +91 -0
- package/dist/runtime/plugins/internal/request-target.d.ts +16 -0
- package/dist/runtime/plugins/internal/request-target.js +17 -0
- package/dist/runtime/roles/has-role.d.ts +21 -0
- package/dist/runtime/roles/has-role.js +51 -0
- package/dist/runtime/server/api/_auth/refresh-handler.d.ts +6 -0
- package/dist/runtime/server/api/_auth/refresh-handler.js +44 -0
- package/dist/runtime/server/api/_auth/refresh.post.d.ts +6 -0
- package/dist/runtime/server/api/_auth/refresh.post.js +5 -0
- package/dist/runtime/server/tsconfig.json +3 -0
- package/dist/runtime/service/index.d.ts +119 -0
- package/dist/runtime/service/index.js +170 -0
- package/dist/runtime/service/new-index.d.ts +24 -0
- package/dist/runtime/service/new-index.js +0 -0
- package/dist/runtime/service/types/async-service.d.ts +59 -0
- package/dist/runtime/service/types/async-service.js +0 -0
- package/dist/runtime/service/types/sync-service.d.ts +28 -0
- package/dist/runtime/service/types/sync-service.js +0 -0
- package/dist/runtime/service/utils/getJwtAsyncService.d.ts +11 -0
- package/dist/runtime/service/utils/getJwtAsyncService.js +16 -0
- package/dist/runtime/storage/nitro.d.ts +61 -0
- package/dist/runtime/storage/nitro.js +124 -0
- package/dist/runtime/storage/types.d.ts +27 -0
- package/dist/runtime/storage/types.js +0 -0
- package/dist/runtime/strategy/base.d.ts +40 -0
- package/dist/runtime/strategy/base.js +88 -0
- package/dist/runtime/strategy/jwt/callback.d.ts +60 -0
- package/dist/runtime/strategy/jwt/callback.js +0 -0
- package/dist/runtime/strategy/jwt/index.d.ts +137 -0
- package/dist/runtime/strategy/jwt/index.js +748 -0
- package/dist/runtime/strategy/jwt/jwt-crypto.d.ts +122 -0
- package/dist/runtime/strategy/jwt/jwt-crypto.js +298 -0
- package/dist/runtime/strategy/jwt/types.d.ts +68 -0
- package/dist/runtime/strategy/jwt/types.js +0 -0
- package/dist/runtime/strategy/types.d.ts +68 -0
- package/dist/runtime/strategy/types.js +0 -0
- package/dist/runtime/types/2fac.d.ts +32 -0
- package/dist/runtime/types/2fac.js +0 -0
- package/dist/runtime/types/authkit-types.d.ts +6 -0
- package/dist/runtime/types/authkit-types.js +0 -0
- package/dist/runtime/types/callbacks.d.ts +133 -0
- package/dist/runtime/types/callbacks.js +0 -0
- package/dist/runtime/types/cookie.d.ts +47 -0
- package/dist/runtime/types/cookie.js +0 -0
- package/dist/runtime/types/expand.d.ts +3 -0
- package/dist/runtime/types/expand.js +0 -0
- package/dist/runtime/types/index.d.ts +52 -0
- package/dist/runtime/types/index.js +0 -0
- package/dist/runtime/types/module-options.d.ts +33 -0
- package/dist/runtime/types/module-options.js +0 -0
- package/dist/runtime/types/runtime-config.d.ts +2 -0
- package/dist/runtime/types/runtime-config.js +47 -0
- package/dist/runtime/types/token.d.ts +42 -0
- package/dist/runtime/types/token.js +12 -0
- package/dist/runtime/utils/context-helpers.d.ts +4 -0
- package/dist/runtime/utils/context-helpers.js +10 -0
- package/dist/runtime/utils/get-module-options.d.ts +2 -0
- package/dist/runtime/utils/get-module-options.js +18 -0
- package/dist/runtime/utils/password.d.ts +23 -0
- package/dist/runtime/utils/password.js +18 -0
- package/dist/runtime/utils/uuid.d.ts +187 -0
- package/dist/runtime/utils/uuid.js +345 -0
- package/dist/types.d.mts +7 -0
- package/package.json +65 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createError, getHeader } from "h3";
|
|
2
|
+
import { consola } from "consola";
|
|
3
|
+
import { getAuthkit } from "../../../service/utils/getJwtAsyncService.js";
|
|
4
|
+
function assertNotCrossSite(event) {
|
|
5
|
+
const secFetchSite = getHeader(event, "sec-fetch-site");
|
|
6
|
+
if (secFetchSite === "cross-site") {
|
|
7
|
+
throw createError({
|
|
8
|
+
statusCode: 403,
|
|
9
|
+
statusMessage: "Forbidden"
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export async function handleRefreshRequest(event) {
|
|
14
|
+
assertNotCrossSite(event);
|
|
15
|
+
let refreshResult;
|
|
16
|
+
try {
|
|
17
|
+
const auth = getAuthkit();
|
|
18
|
+
refreshResult = await auth.refresh();
|
|
19
|
+
} catch (error) {
|
|
20
|
+
consola.error("[nuxt-authkit] Unexpected error while refreshing token:", error);
|
|
21
|
+
throw createError({
|
|
22
|
+
statusCode: 500,
|
|
23
|
+
statusMessage: "Internal Server Error"
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (!refreshResult.success) {
|
|
27
|
+
consola.debug("[nuxt-authkit] Refresh rejected:", refreshResult.error);
|
|
28
|
+
throw createError({
|
|
29
|
+
statusCode: 401,
|
|
30
|
+
statusMessage: "Unauthorized",
|
|
31
|
+
data: {
|
|
32
|
+
error: {
|
|
33
|
+
code: refreshResult.error?.code ?? "REFRESH_TOKEN_INVALID",
|
|
34
|
+
recoverable: refreshResult.error?.recoverable ?? false
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
status: "ok",
|
|
41
|
+
message: "Token refreshed",
|
|
42
|
+
data: refreshResult
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { H3Event } from 'h3';
|
|
2
|
+
import type { AuthResult, VerifyAuthResult } from '../types/index.js';
|
|
3
|
+
import type { IAuthStrategy } from '../strategy/types.js';
|
|
4
|
+
import type { AuthkitCredentials } from '#nuxt-authkit';
|
|
5
|
+
import type { IAuthServiceAsync } from './types/async-service.js';
|
|
6
|
+
import type { Expand } from '../types/expand.js';
|
|
7
|
+
/**
|
|
8
|
+
* Singleton Authentication Service
|
|
9
|
+
*
|
|
10
|
+
* Manages authentication using a pluggable strategy pattern.
|
|
11
|
+
* Types are automatically inferred from module augmentation.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // 1. Define types once:
|
|
16
|
+
* declare module 'nuxt-auth-toolkit' {
|
|
17
|
+
* interface AuthKitTypes {
|
|
18
|
+
* User: MyUser
|
|
19
|
+
* Credentials: MyCredentials
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* // 2. Use everywhere without type parameters:
|
|
24
|
+
* const auth = getNuxtAuthkit() // Automatically typed!
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare class AuthenticationService implements IAuthServiceAsync {
|
|
28
|
+
private static instance;
|
|
29
|
+
private static isInitialized;
|
|
30
|
+
private readonly strategy;
|
|
31
|
+
private constructor();
|
|
32
|
+
/**
|
|
33
|
+
* Initialize the singleton instance
|
|
34
|
+
* Must be called once during app startup
|
|
35
|
+
*/
|
|
36
|
+
static init(strategy: IAuthStrategy): AuthenticationService;
|
|
37
|
+
/**
|
|
38
|
+
* Get the singleton instance
|
|
39
|
+
* Types are automatically inferred from module augmentation
|
|
40
|
+
*/
|
|
41
|
+
static getInstance(): AuthenticationService;
|
|
42
|
+
/**
|
|
43
|
+
* Check if service is initialized
|
|
44
|
+
*/
|
|
45
|
+
static isReady(): boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Reset the singleton (useful for testing)
|
|
48
|
+
*/
|
|
49
|
+
static reset(): void;
|
|
50
|
+
/**
|
|
51
|
+
* Get the strategy name
|
|
52
|
+
*/
|
|
53
|
+
get strategyName(): string;
|
|
54
|
+
/**
|
|
55
|
+
* Authenticate user with credentials
|
|
56
|
+
*/
|
|
57
|
+
authenticate(credentials: AuthkitCredentials, options?: {
|
|
58
|
+
accessExpiresIn?: number;
|
|
59
|
+
refreshExpiresIn?: number;
|
|
60
|
+
}, event?: H3Event): Promise<AuthResult>;
|
|
61
|
+
/**
|
|
62
|
+
* Verify a token
|
|
63
|
+
*/
|
|
64
|
+
verify(event?: H3Event, options?: any): Promise<VerifyAuthResult>;
|
|
65
|
+
/**
|
|
66
|
+
* Verify a bare access token, with no request context.
|
|
67
|
+
*
|
|
68
|
+
* Synchronous by design: this is signature + claim validation only, and its
|
|
69
|
+
* callers (WebSocket upgrade handlers, workers) are often outside a request.
|
|
70
|
+
*/
|
|
71
|
+
verifyToken(token: string): VerifyAuthResult;
|
|
72
|
+
/**
|
|
73
|
+
* Issue tokens for an already-identified user, bypassing credentials.
|
|
74
|
+
*/
|
|
75
|
+
issueTokens(userId: string, options?: {
|
|
76
|
+
roles?: string[];
|
|
77
|
+
claims?: Record<string, unknown>;
|
|
78
|
+
accessExpiresIn?: number;
|
|
79
|
+
refreshExpiresIn?: number;
|
|
80
|
+
}, event?: H3Event): Promise<AuthResult>;
|
|
81
|
+
/**
|
|
82
|
+
* Refresh tokens
|
|
83
|
+
*/
|
|
84
|
+
refresh(event?: H3Event, options?: any): Promise<AuthResult>;
|
|
85
|
+
/**
|
|
86
|
+
* Revoke/invalidate tokens
|
|
87
|
+
*/
|
|
88
|
+
revoke(event?: H3Event): Promise<void>;
|
|
89
|
+
/**
|
|
90
|
+
* Revoke/invalidate tokens during logout (server initiated) - e.g. Nitro scheduled task
|
|
91
|
+
*
|
|
92
|
+
* Throws if the active strategy cannot perform server-side revocation, rather
|
|
93
|
+
* than resolving successfully having done nothing — an operator running an
|
|
94
|
+
* incident force-logout has to be able to trust the answer.
|
|
95
|
+
*/
|
|
96
|
+
revokeServer(options: any, event?: H3Event): Promise<void>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Factory function to create and initialize the singleton
|
|
100
|
+
* Types are automatically inferred from module augmentation
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* // 1. Define your types once (in types/auth.ts):
|
|
105
|
+
* declare module 'nuxt-auth-toolkit' {
|
|
106
|
+
* interface AuthKitTypes {
|
|
107
|
+
* User: { id: number; email: string; name: string }
|
|
108
|
+
* Credentials: { username: string; password: string }
|
|
109
|
+
* }
|
|
110
|
+
* }
|
|
111
|
+
*
|
|
112
|
+
* // 2. Initialize (in server/plugins/auth.ts):
|
|
113
|
+
* export default defineNitroPlugin((nitroApp) => {
|
|
114
|
+
* const strategy = new JwtStrategy({ ... }, callbacks)
|
|
115
|
+
* createAuthenticationService(nitroApp, strategy) // Types inferred!
|
|
116
|
+
* })
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function createNuxtAuthkit<TCredentials = AuthkitCredentials>(strategy: IAuthStrategy): Expand<IAuthServiceAsync<TCredentials>>;
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { useEvent } from "nitropack/runtime/internal/context";
|
|
2
|
+
import { consola } from "consola";
|
|
3
|
+
export class AuthenticationService {
|
|
4
|
+
static instance = null;
|
|
5
|
+
static isInitialized = false;
|
|
6
|
+
strategy;
|
|
7
|
+
constructor(strategy) {
|
|
8
|
+
this.strategy = strategy;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Initialize the singleton instance
|
|
12
|
+
* Must be called once during app startup
|
|
13
|
+
*/
|
|
14
|
+
static init(strategy) {
|
|
15
|
+
if (this.isInitialized && this.instance) {
|
|
16
|
+
return this.instance;
|
|
17
|
+
}
|
|
18
|
+
this.instance = new AuthenticationService(
|
|
19
|
+
strategy
|
|
20
|
+
);
|
|
21
|
+
this.isInitialized = true;
|
|
22
|
+
consola.log({
|
|
23
|
+
message: `Using strategy: \x1B[32m${strategy.name} \x1B`,
|
|
24
|
+
tag: "\x1B[32m nuxt-authkit \x1B"
|
|
25
|
+
});
|
|
26
|
+
return this.instance;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get the singleton instance
|
|
30
|
+
* Types are automatically inferred from module augmentation
|
|
31
|
+
*/
|
|
32
|
+
static getInstance() {
|
|
33
|
+
if (!this.instance || !this.isInitialized) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
"[nuxt-authkit] not initialized. Call createNuxtAuthkit() first in your Nitro plugin."
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
return this.instance;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Check if service is initialized
|
|
42
|
+
*/
|
|
43
|
+
static isReady() {
|
|
44
|
+
return this.isInitialized && this.instance !== null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Reset the singleton (useful for testing)
|
|
48
|
+
*/
|
|
49
|
+
static reset() {
|
|
50
|
+
this.instance = null;
|
|
51
|
+
this.isInitialized = false;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get the strategy name
|
|
55
|
+
*/
|
|
56
|
+
get strategyName() {
|
|
57
|
+
return this.strategy.name;
|
|
58
|
+
}
|
|
59
|
+
// ========== Authentication Methods ==========
|
|
60
|
+
/**
|
|
61
|
+
* Authenticate user with credentials
|
|
62
|
+
*/
|
|
63
|
+
async authenticate(credentials, options, event) {
|
|
64
|
+
const currentEvent = event ? event : typeof useEvent === "function" ? useEvent() : void 0;
|
|
65
|
+
try {
|
|
66
|
+
return await this.strategy.authenticate(credentials, currentEvent, options);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error("[nuxt-authkit] Authentication error:", error);
|
|
69
|
+
throw error;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Verify a token
|
|
74
|
+
*/
|
|
75
|
+
async verify(event, options) {
|
|
76
|
+
const currentEvent = event ? event : typeof useEvent === "function" ? useEvent() : void 0;
|
|
77
|
+
try {
|
|
78
|
+
return await this.strategy.verify(currentEvent, options);
|
|
79
|
+
} catch (error) {
|
|
80
|
+
console.error("[nuxt-authkit] Token verification error:", error);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Verify a bare access token, with no request context.
|
|
86
|
+
*
|
|
87
|
+
* Synchronous by design: this is signature + claim validation only, and its
|
|
88
|
+
* callers (WebSocket upgrade handlers, workers) are often outside a request.
|
|
89
|
+
*/
|
|
90
|
+
verifyToken(token) {
|
|
91
|
+
if (typeof this.strategy.verifyToken !== "function") {
|
|
92
|
+
throw new TypeError(
|
|
93
|
+
`[nuxt-authkit] The "${this.strategy.name}" strategy does not support context-free token verification.`
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return this.strategy.verifyToken(token);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Issue tokens for an already-identified user, bypassing credentials.
|
|
100
|
+
*/
|
|
101
|
+
async issueTokens(userId, options, event) {
|
|
102
|
+
if (typeof this.strategy.issueTokens !== "function") {
|
|
103
|
+
throw new TypeError(
|
|
104
|
+
`[nuxt-authkit] The "${this.strategy.name}" strategy does not support issuing tokens without credentials.`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
const currentEvent = event ? event : typeof useEvent === "function" ? useEvent() : void 0;
|
|
108
|
+
try {
|
|
109
|
+
return await this.strategy.issueTokens(userId, currentEvent, options);
|
|
110
|
+
} catch (error) {
|
|
111
|
+
console.error("[nuxt-authkit] Token issuance error:", error);
|
|
112
|
+
throw error;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Refresh tokens
|
|
117
|
+
*/
|
|
118
|
+
async refresh(event, options) {
|
|
119
|
+
const currentEvent = event ? event : typeof useEvent === "function" ? useEvent() : void 0;
|
|
120
|
+
try {
|
|
121
|
+
return await this.strategy.refresh(currentEvent, options);
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("[nuxt-authkit] Token refresh error:", error);
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Revoke/invalidate tokens
|
|
129
|
+
*/
|
|
130
|
+
async revoke(event) {
|
|
131
|
+
const currentEvent = event ? event : typeof useEvent === "function" ? useEvent() : void 0;
|
|
132
|
+
try {
|
|
133
|
+
await this.strategy.revoke(currentEvent);
|
|
134
|
+
} catch (error) {
|
|
135
|
+
console.error("[nuxt-authkit] Token revocation error:", error);
|
|
136
|
+
throw error;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Revoke/invalidate tokens during logout (server initiated) - e.g. Nitro scheduled task
|
|
141
|
+
*
|
|
142
|
+
* Throws if the active strategy cannot perform server-side revocation, rather
|
|
143
|
+
* than resolving successfully having done nothing — an operator running an
|
|
144
|
+
* incident force-logout has to be able to trust the answer.
|
|
145
|
+
*/
|
|
146
|
+
async revokeServer(options, event) {
|
|
147
|
+
if (typeof this.strategy.revokeServer !== "function") {
|
|
148
|
+
throw new TypeError(
|
|
149
|
+
`[nuxt-authkit] The "${this.strategy.name}" strategy does not support server-initiated revocation.`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
let currentEvent = event;
|
|
153
|
+
if (!currentEvent && typeof useEvent === "function") {
|
|
154
|
+
try {
|
|
155
|
+
currentEvent = useEvent();
|
|
156
|
+
} catch {
|
|
157
|
+
currentEvent = void 0;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
await this.strategy.revokeServer(options, currentEvent);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error("[nuxt-authkit] Server token revocation error:", error);
|
|
164
|
+
throw error;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export function createNuxtAuthkit(strategy) {
|
|
169
|
+
return AuthenticationService.init(strategy);
|
|
170
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { H3Event } from "h3";
|
|
2
|
+
import type { AuthResult, VerifyAuthResult } from "../types/index.js";
|
|
3
|
+
import type { Expand } from "../types/expand.js";
|
|
4
|
+
import type { AuthkitCredentials } from './../types/authkit-types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Base interface that all authentication strategies must implement
|
|
7
|
+
*/
|
|
8
|
+
export interface IAuthService<TCredentials = AuthkitCredentials> {
|
|
9
|
+
/**
|
|
10
|
+
* Authenticate user with credentials
|
|
11
|
+
* Returns tokens and user data on success
|
|
12
|
+
*/
|
|
13
|
+
authenticate(credentials: TCredentials, event?: H3Event): Promise<Expand<AuthResult>>;
|
|
14
|
+
/**
|
|
15
|
+
* Verify a that the current authentication method is valid
|
|
16
|
+
*/
|
|
17
|
+
verify(event?: H3Event): Promise<VerifyAuthResult>;
|
|
18
|
+
refresh(event?: H3Event): Promise<AuthResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Revoke/invalidate tokens
|
|
21
|
+
* Used during logout
|
|
22
|
+
*/
|
|
23
|
+
revoke(event?: H3Event): Promise<void>;
|
|
24
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { AuthResult, VerifyAuthResult } from "../../types/index.js";
|
|
2
|
+
import type { Expand } from "../../types/expand.js";
|
|
3
|
+
import type { AuthkitCredentials } from './../../types/authkit-types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Base interface that auth service must implement when experimental 'asyncContext' is used,
|
|
6
|
+
* meaning event is captured via 'useEvent' internally at each method call. Use in nuxt/nitro
|
|
7
|
+
* context only.
|
|
8
|
+
*/
|
|
9
|
+
export interface IAuthServiceAsync<TCredentials = AuthkitCredentials, TServerRevokeOptions = {}, TVerifyOptions = {}, TRefreshOptions = {}> {
|
|
10
|
+
/**
|
|
11
|
+
* Authenticate user with credentials
|
|
12
|
+
* Returns tokens and user data on success
|
|
13
|
+
*/
|
|
14
|
+
authenticate(credentials: TCredentials, options?: {
|
|
15
|
+
accessExpiresIn?: number;
|
|
16
|
+
refreshExpiresIn?: number;
|
|
17
|
+
}): Promise<Expand<AuthResult>>;
|
|
18
|
+
/**
|
|
19
|
+
* Verify a that the current authentication method is valid
|
|
20
|
+
*/
|
|
21
|
+
verify(options?: Expand<TVerifyOptions>): Promise<VerifyAuthResult>;
|
|
22
|
+
/**
|
|
23
|
+
* Verify a bare access token with no request context — for callers that hold
|
|
24
|
+
* a token but no H3 event (WebSocket peers, background workers, signed asset
|
|
25
|
+
* URLs). Synchronous: no store lookup is involved, only signature and claims.
|
|
26
|
+
*
|
|
27
|
+
* Throws if the active strategy has no context-free token to verify.
|
|
28
|
+
*/
|
|
29
|
+
verifyToken(token: string): VerifyAuthResult;
|
|
30
|
+
/**
|
|
31
|
+
* Issue a token pair for `userId` without presenting credentials, for flows
|
|
32
|
+
* where identity is already established (OAuth callback, accepted invite,
|
|
33
|
+
* impersonation, device linking).
|
|
34
|
+
*
|
|
35
|
+
* This authenticates nobody — the caller asserts the subject. Never reach it
|
|
36
|
+
* from a route that has not already proven who the user is.
|
|
37
|
+
*/
|
|
38
|
+
issueTokens(userId: string, options?: {
|
|
39
|
+
roles?: string[];
|
|
40
|
+
claims?: Record<string, unknown>;
|
|
41
|
+
accessExpiresIn?: number;
|
|
42
|
+
refreshExpiresIn?: number;
|
|
43
|
+
}): Promise<Expand<AuthResult>>;
|
|
44
|
+
/**
|
|
45
|
+
* Exchange the refresh token for a new access token.
|
|
46
|
+
* The refresh token is rotated unless rotation is disabled.
|
|
47
|
+
*/
|
|
48
|
+
refresh(options?: Expand<TRefreshOptions>): Promise<AuthResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Revoke/invalidate tokens
|
|
51
|
+
* Used during logout
|
|
52
|
+
*/
|
|
53
|
+
revoke(): Promise<void>;
|
|
54
|
+
/**
|
|
55
|
+
* Server-initiated revocation. Throws if the strategy has no
|
|
56
|
+
* `onRevokeServer` callback configured — it will not silently no-op.
|
|
57
|
+
*/
|
|
58
|
+
revokeServer(options: Expand<TServerRevokeOptions>): Promise<void>;
|
|
59
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { H3Event } from "h3";
|
|
2
|
+
import type { AuthResult, VerifyAuthResult } from "../../types/index.js";
|
|
3
|
+
import type { Expand } from "../../types/expand.js";
|
|
4
|
+
import type { AuthkitCredentials } from '../../types/authkit-types.js';
|
|
5
|
+
export interface RevokeServerOptions {
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Base interface that auth service must implement when experimental 'asyncContext' is used,
|
|
9
|
+
* meaning event is captured via 'useEvent' internally at each method call. Use in nuxt/nitro
|
|
10
|
+
* context only.
|
|
11
|
+
*/
|
|
12
|
+
export interface IAuthService<TCredentials = AuthkitCredentials> {
|
|
13
|
+
/**
|
|
14
|
+
* Authenticate user with credentials
|
|
15
|
+
* Returns tokens and user data on success
|
|
16
|
+
*/
|
|
17
|
+
authenticate(credentials: TCredentials, event: H3Event): Promise<Expand<AuthResult>>;
|
|
18
|
+
/**
|
|
19
|
+
* Verify a that the current authentication method is valid
|
|
20
|
+
*/
|
|
21
|
+
verify(event: H3Event): Promise<VerifyAuthResult>;
|
|
22
|
+
refresh(event: H3Event): Promise<AuthResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Revoke/invalidate tokens during logout (client initiated)
|
|
25
|
+
*/
|
|
26
|
+
revoke(event: H3Event): Promise<void>;
|
|
27
|
+
revokeServer(options: Expand<RevokeServerOptions>): Promise<void>;
|
|
28
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IAuthServiceAsync } from '../types/async-service.js';
|
|
2
|
+
import type { AuthkitCredentials } from '../../types/authkit-types.js';
|
|
3
|
+
import type { Expand } from '../../types/expand.js';
|
|
4
|
+
import type { JWTServerRevokeOptions, JWTVerifyOptions, JWTRefreshOptions } from '../../strategy/jwt/types.js';
|
|
5
|
+
/**
|
|
6
|
+
* Alias for useAuthenticationService
|
|
7
|
+
* Provides a more Nuxt-friendly name
|
|
8
|
+
* Types are automatically inferred from module augmentation
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export declare function getAuthkit<TCredentials = AuthkitCredentials>(): Expand<IAuthServiceAsync<TCredentials, JWTServerRevokeOptions, JWTVerifyOptions, JWTRefreshOptions>>;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { AuthenticationService } from "../index.js";
|
|
2
|
+
export function getAuthkit() {
|
|
3
|
+
const authService = AuthenticationService.getInstance();
|
|
4
|
+
return {
|
|
5
|
+
authenticate: (credentials, options) => authService.authenticate(credentials, options),
|
|
6
|
+
// `undefined` is passed explicitly for the event slot: the service resolves
|
|
7
|
+
// the ambient event itself, and passing options positionally here used to
|
|
8
|
+
// land them in that slot and break every verify({ accessToken }) call.
|
|
9
|
+
verify: (options) => authService.verify(void 0, options),
|
|
10
|
+
verifyToken: (token) => authService.verifyToken(token),
|
|
11
|
+
issueTokens: (userId, options) => authService.issueTokens(userId, options),
|
|
12
|
+
refresh: (options) => authService.refresh(void 0, options),
|
|
13
|
+
revoke: () => authService.revoke(),
|
|
14
|
+
revokeServer: (options) => authService.revokeServer(options)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { AuthStorage, StorageOptions, StorageAdapter } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for Nitro storage
|
|
4
|
+
*/
|
|
5
|
+
export interface NitroStorageConfig {
|
|
6
|
+
/**
|
|
7
|
+
* Prefix for all storage keys
|
|
8
|
+
* Useful for namespacing auth data
|
|
9
|
+
* @default 'auth'
|
|
10
|
+
*/
|
|
11
|
+
prefix?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Default TTL for items (in seconds)
|
|
14
|
+
* If not provided, items don't expire
|
|
15
|
+
*/
|
|
16
|
+
ttl?: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* AuthStorage implementation using Nitro's storage via dependency injection
|
|
20
|
+
*
|
|
21
|
+
* NOTE: not currently used by `JWTStrategy` — refresh-token persistence is
|
|
22
|
+
* delegated entirely to the `onStoreRefreshToken` / `onGetRefreshToken` /
|
|
23
|
+
* `onRemoveRefreshToken` callbacks. Kept as a general-purpose adapter.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```ts
|
|
27
|
+
* const storage = useStorage('redis')
|
|
28
|
+
* const authStorage = new NitroStorage(storage, {
|
|
29
|
+
* prefix: 'auth',
|
|
30
|
+
* ttl: 3600
|
|
31
|
+
* })
|
|
32
|
+
*
|
|
33
|
+
* // Keys are escaped, so a ':' inside an untrusted id cannot forge a namespace.
|
|
34
|
+
* await authStorage.set(userId, token)
|
|
35
|
+
* const token = await authStorage.get(userId)
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare class NitroStorage implements AuthStorage {
|
|
39
|
+
private readonly config;
|
|
40
|
+
private readonly storage;
|
|
41
|
+
constructor(storage: StorageAdapter, config?: NitroStorageConfig);
|
|
42
|
+
/**
|
|
43
|
+
* `:` is the namespace separator, so a key containing one could otherwise
|
|
44
|
+
* collide with — or overwrite — another namespace's entry when the key is
|
|
45
|
+
* derived from an untrusted identifier. Escape it (and the escape character)
|
|
46
|
+
* before joining.
|
|
47
|
+
*/
|
|
48
|
+
private buildKey;
|
|
49
|
+
get(key: string): Promise<string | null>;
|
|
50
|
+
set(key: string, value: string, options?: StorageOptions): Promise<void>;
|
|
51
|
+
remove(key: string): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Clear all items with this prefix
|
|
54
|
+
* Note: This only works efficiently with some drivers (filesystem, memory)
|
|
55
|
+
*/
|
|
56
|
+
clear(): Promise<void>;
|
|
57
|
+
has(key: string): Promise<boolean>;
|
|
58
|
+
getMany(keys: string[]): Promise<Map<string, string | null>>;
|
|
59
|
+
setMany(entries: Array<[string, string]>, options?: StorageOptions): Promise<void>;
|
|
60
|
+
keys(): Promise<string[]>;
|
|
61
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
export class NitroStorage {
|
|
2
|
+
config;
|
|
3
|
+
storage;
|
|
4
|
+
constructor(storage, config = {}) {
|
|
5
|
+
this.storage = storage;
|
|
6
|
+
this.config = {
|
|
7
|
+
prefix: config.prefix ?? "auth",
|
|
8
|
+
ttl: config.ttl ?? 0
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* `:` is the namespace separator, so a key containing one could otherwise
|
|
13
|
+
* collide with — or overwrite — another namespace's entry when the key is
|
|
14
|
+
* derived from an untrusted identifier. Escape it (and the escape character)
|
|
15
|
+
* before joining.
|
|
16
|
+
*/
|
|
17
|
+
buildKey(key) {
|
|
18
|
+
const escaped = key.replace(/\\/g, "\\\\").replace(/:/g, "\\:");
|
|
19
|
+
return this.config.prefix ? `${this.config.prefix}:${escaped}` : escaped;
|
|
20
|
+
}
|
|
21
|
+
async get(key) {
|
|
22
|
+
const fullKey = this.buildKey(key);
|
|
23
|
+
try {
|
|
24
|
+
const value = await this.storage.getItem(fullKey);
|
|
25
|
+
if (!value) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === "object" && "data" in value && "expiresAt" in value) {
|
|
29
|
+
const wrapped = value;
|
|
30
|
+
if (Date.now() > wrapped.expiresAt) {
|
|
31
|
+
await this.storage.removeItem(fullKey);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
return wrapped.data;
|
|
35
|
+
}
|
|
36
|
+
return typeof value === "string" ? value : JSON.stringify(value);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error(
|
|
39
|
+
`[NitroStorage] Error getting key "${fullKey}":`,
|
|
40
|
+
error
|
|
41
|
+
);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async set(key, value, options) {
|
|
46
|
+
const fullKey = this.buildKey(key);
|
|
47
|
+
try {
|
|
48
|
+
const ttl = options?.maxAge ?? this.config.ttl;
|
|
49
|
+
if (ttl > 0) {
|
|
50
|
+
const expiresAt = Date.now() + ttl * 1e3;
|
|
51
|
+
const data = {
|
|
52
|
+
data: value,
|
|
53
|
+
expiresAt
|
|
54
|
+
};
|
|
55
|
+
await this.storage.setItem(fullKey, data);
|
|
56
|
+
} else {
|
|
57
|
+
await this.storage.setItem(fullKey, value);
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.error(
|
|
61
|
+
`[NitroStorage] Error setting key "${fullKey}":`,
|
|
62
|
+
error
|
|
63
|
+
);
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
async remove(key) {
|
|
68
|
+
const fullKey = this.buildKey(key);
|
|
69
|
+
try {
|
|
70
|
+
await this.storage.removeItem(fullKey);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
console.error(
|
|
73
|
+
`[NitroStorage] Error removing key "${fullKey}":`,
|
|
74
|
+
error
|
|
75
|
+
);
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Clear all items with this prefix
|
|
81
|
+
* Note: This only works efficiently with some drivers (filesystem, memory)
|
|
82
|
+
*/
|
|
83
|
+
async clear() {
|
|
84
|
+
try {
|
|
85
|
+
const keys = await this.storage.getKeys(`${this.config.prefix}:`);
|
|
86
|
+
await Promise.all(keys.map((key) => this.storage.removeItem(key)));
|
|
87
|
+
} catch (error) {
|
|
88
|
+
console.error("[NitroStorage] Error clearing storage:", error);
|
|
89
|
+
throw error;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
async has(key) {
|
|
93
|
+
const value = await this.get(key);
|
|
94
|
+
return value !== null;
|
|
95
|
+
}
|
|
96
|
+
async getMany(keys) {
|
|
97
|
+
const results = /* @__PURE__ */ new Map();
|
|
98
|
+
await Promise.all(
|
|
99
|
+
keys.map(async (key) => {
|
|
100
|
+
const value = await this.get(key);
|
|
101
|
+
results.set(key, value);
|
|
102
|
+
})
|
|
103
|
+
);
|
|
104
|
+
return results;
|
|
105
|
+
}
|
|
106
|
+
async setMany(entries, options) {
|
|
107
|
+
await Promise.all(
|
|
108
|
+
entries.map(([key, value]) => this.set(key, value, options))
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
async keys() {
|
|
112
|
+
try {
|
|
113
|
+
const allKeys = await this.storage.getKeys(
|
|
114
|
+
`${this.config.prefix}:`
|
|
115
|
+
);
|
|
116
|
+
return allKeys.map(
|
|
117
|
+
(key) => key.startsWith(this.config.prefix + ":") ? key.slice(this.config.prefix.length + 1) : key
|
|
118
|
+
);
|
|
119
|
+
} catch (error) {
|
|
120
|
+
console.error("[NitroStorage] Error getting keys:", error);
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|