@geekmidas/auth 2.0.2 → 10.0.0-alpha.0
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/dist/cacheTokenStorage-B7sOoysu.d.cts.map +1 -1
- package/dist/{cacheTokenStorage-_tq4Kmhc.d.mts → cacheTokenStorage-DaqxdNxI.d.mts} +1 -1
- package/dist/{cacheTokenStorage-_tq4Kmhc.d.mts.map → cacheTokenStorage-DaqxdNxI.d.mts.map} +1 -1
- package/dist/cacheTokenStorage.d.mts +1 -1
- package/dist/client.d.mts +1 -1
- package/dist/cookies-Bc6gUjb6.cjs +30 -0
- package/dist/cookies-Bc6gUjb6.cjs.map +1 -0
- package/dist/cookies-C80f8JtE.mjs +24 -0
- package/dist/cookies-C80f8JtE.mjs.map +1 -0
- package/dist/cookies.cjs +3 -0
- package/dist/cookies.d.cts +14 -0
- package/dist/cookies.d.cts.map +1 -0
- package/dist/cookies.d.mts +14 -0
- package/dist/cookies.d.mts.map +1 -0
- package/dist/cookies.mjs +3 -0
- package/dist/hono/jwt.cjs +4 -7
- package/dist/hono/jwt.cjs.map +1 -1
- package/dist/hono/jwt.d.cts.map +1 -1
- package/dist/hono/jwt.d.mts +1 -1
- package/dist/hono/jwt.d.mts.map +1 -1
- package/dist/hono/jwt.mjs +5 -7
- package/dist/hono/jwt.mjs.map +1 -1
- package/dist/hono/oidc.cjs +4 -7
- package/dist/hono/oidc.cjs.map +1 -1
- package/dist/hono/oidc.d.cts.map +1 -1
- package/dist/hono/oidc.d.mts +2 -2
- package/dist/hono/oidc.d.mts.map +1 -1
- package/dist/hono/oidc.mjs +5 -7
- package/dist/hono/oidc.mjs.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/{jwt-D88cPJyT.mjs → jwt-bZR2_t8F.mjs} +1 -1
- package/dist/{jwt-D88cPJyT.mjs.map → jwt-bZR2_t8F.mjs.map} +1 -1
- package/dist/{jwt-Bo_eACEi.cjs → jwt-clq6I6vG.cjs} +1 -1
- package/dist/{jwt-Bo_eACEi.cjs.map → jwt-clq6I6vG.cjs.map} +1 -1
- package/dist/{jwt-BqLlRkw-.d.mts → jwt-kejix7rd.d.mts} +1 -1
- package/dist/{jwt-BqLlRkw-.d.mts.map → jwt-kejix7rd.d.mts.map} +1 -1
- package/dist/jwt.cjs +1 -1
- package/dist/jwt.d.mts +1 -1
- package/dist/jwt.mjs +1 -1
- package/dist/lambda/jwt.cjs +4 -5
- package/dist/lambda/jwt.cjs.map +1 -1
- package/dist/lambda/jwt.d.cts.map +1 -1
- package/dist/lambda/jwt.d.mts +1 -1
- package/dist/lambda/jwt.d.mts.map +1 -1
- package/dist/lambda/jwt.mjs +5 -5
- package/dist/lambda/jwt.mjs.map +1 -1
- package/dist/lambda/oidc.cjs +4 -5
- package/dist/lambda/oidc.cjs.map +1 -1
- package/dist/lambda/oidc.d.cts.map +1 -1
- package/dist/lambda/oidc.d.mts +2 -2
- package/dist/lambda/oidc.d.mts.map +1 -1
- package/dist/lambda/oidc.mjs +5 -5
- package/dist/lambda/oidc.mjs.map +1 -1
- package/dist/{oidc-KA30gMlx.mjs → oidc-BYHREHir.mjs} +1 -1
- package/dist/{oidc-KA30gMlx.mjs.map → oidc-BYHREHir.mjs.map} +1 -1
- package/dist/{oidc-DTRgBBl6.d.mts → oidc-Ca7GYwmM.d.mts} +2 -2
- package/dist/{oidc-DTRgBBl6.d.mts.map → oidc-Ca7GYwmM.d.mts.map} +1 -1
- package/dist/{oidc-C0KyYaAK.cjs → oidc-jdMfVyyC.cjs} +1 -1
- package/dist/{oidc-C0KyYaAK.cjs.map → oidc-jdMfVyyC.cjs.map} +1 -1
- package/dist/oidc.cjs +1 -1
- package/dist/oidc.d.mts +2 -2
- package/dist/oidc.mjs +1 -1
- package/dist/server.cjs +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +1 -1
- package/dist/{tokenManager-Ci2Tgxp8.mjs → tokenManager-BbylTW7F.mjs} +1 -1
- package/dist/{tokenManager-Ci2Tgxp8.mjs.map → tokenManager-BbylTW7F.mjs.map} +1 -1
- package/dist/{tokenManager-RrwjL0y-.d.mts → tokenManager-CosB7J0i.d.mts} +1 -1
- package/dist/{tokenManager-RrwjL0y-.d.mts.map → tokenManager-CosB7J0i.d.mts.map} +1 -1
- package/dist/{tokenManager-CvEMgxOp.cjs → tokenManager-PQ-OI_NU.cjs} +1 -1
- package/dist/{tokenManager-CvEMgxOp.cjs.map → tokenManager-PQ-OI_NU.cjs.map} +1 -1
- package/dist/tokenManager.cjs +1 -1
- package/dist/tokenManager.d.mts +1 -1
- package/dist/tokenManager.mjs +1 -1
- package/package.json +6 -3
- package/CHANGELOG.md +0 -39
- package/src/__tests__/cacheTokenStorage.spec.ts +0 -213
- package/src/__tests__/client.spec.ts +0 -465
- package/src/__tests__/hono-jwt.spec.ts +0 -232
- package/src/__tests__/hono-oidc.spec.ts +0 -605
- package/src/__tests__/jwt.spec.ts +0 -197
- package/src/__tests__/lambda-jwt.spec.ts +0 -278
- package/src/__tests__/lambda-oidc.spec.ts +0 -624
- package/src/__tests__/oidc.spec.ts +0 -405
- package/src/__tests__/tokenManager.spec.ts +0 -259
- package/src/cacheTokenStorage.ts +0 -43
- package/src/client.ts +0 -220
- package/src/hono/jwt.ts +0 -142
- package/src/hono/oidc.ts +0 -170
- package/src/index.ts +0 -3
- package/src/jwt.ts +0 -106
- package/src/lambda/jwt.ts +0 -210
- package/src/lambda/oidc.ts +0 -203
- package/src/oidc.ts +0 -156
- package/src/server.ts +0 -8
- package/src/tokenManager.ts +0 -106
- package/tsconfig.json +0 -10
- package/tsdown.config.ts +0 -3
package/src/client.ts
DELETED
|
@@ -1,220 +0,0 @@
|
|
|
1
|
-
export interface TokenStorage {
|
|
2
|
-
getAccessToken(): Promise<string | null> | string | null;
|
|
3
|
-
setAccessToken(token: string, ttl?: number): Promise<void> | void;
|
|
4
|
-
getRefreshToken(): Promise<string | null> | string | null;
|
|
5
|
-
setRefreshToken(token: string, ttl?: number): Promise<void> | void;
|
|
6
|
-
clearTokens(): Promise<void> | void;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export class LocalStorageTokenStorage implements TokenStorage {
|
|
10
|
-
private accessTokenKey: string;
|
|
11
|
-
private refreshTokenKey: string;
|
|
12
|
-
|
|
13
|
-
constructor(
|
|
14
|
-
accessTokenKey: string = 'access_token',
|
|
15
|
-
refreshTokenKey: string = 'refresh_token',
|
|
16
|
-
) {
|
|
17
|
-
this.accessTokenKey = accessTokenKey;
|
|
18
|
-
this.refreshTokenKey = refreshTokenKey;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
getAccessToken(): string | null {
|
|
22
|
-
if (typeof window === 'undefined') return null;
|
|
23
|
-
return localStorage.getItem(this.accessTokenKey);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
setAccessToken(token: string, _ttl?: number): void {
|
|
27
|
-
if (typeof window === 'undefined') return;
|
|
28
|
-
localStorage.setItem(this.accessTokenKey, token);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
getRefreshToken(): string | null {
|
|
32
|
-
if (typeof window === 'undefined') return null;
|
|
33
|
-
return localStorage.getItem(this.refreshTokenKey);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
setRefreshToken(token: string, _ttl?: number): void {
|
|
37
|
-
if (typeof window === 'undefined') return;
|
|
38
|
-
localStorage.setItem(this.refreshTokenKey, token);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
clearTokens(): void {
|
|
42
|
-
if (typeof window === 'undefined') return;
|
|
43
|
-
localStorage.removeItem(this.accessTokenKey);
|
|
44
|
-
localStorage.removeItem(this.refreshTokenKey);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export class MemoryTokenStorage implements TokenStorage {
|
|
49
|
-
private accessToken: string | null = null;
|
|
50
|
-
private refreshToken: string | null = null;
|
|
51
|
-
|
|
52
|
-
getAccessToken(): string | null {
|
|
53
|
-
return this.accessToken;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
setAccessToken(token: string, _ttl?: number): void {
|
|
57
|
-
this.accessToken = token;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
getRefreshToken(): string | null {
|
|
61
|
-
return this.refreshToken;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
setRefreshToken(token: string, _ttl?: number): void {
|
|
65
|
-
this.refreshToken = token;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
clearTokens(): void {
|
|
69
|
-
this.accessToken = null;
|
|
70
|
-
this.refreshToken = null;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export interface TokenClientOptions {
|
|
75
|
-
storage?: TokenStorage;
|
|
76
|
-
refreshEndpoint?: string;
|
|
77
|
-
onTokenRefresh?: (tokens: {
|
|
78
|
-
accessToken: string;
|
|
79
|
-
refreshToken?: string;
|
|
80
|
-
}) => void;
|
|
81
|
-
onTokenExpired?: () => void;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export class TokenClient {
|
|
85
|
-
private storage: TokenStorage;
|
|
86
|
-
private refreshEndpoint?: string;
|
|
87
|
-
private onTokenRefresh?: (tokens: {
|
|
88
|
-
accessToken: string;
|
|
89
|
-
refreshToken?: string;
|
|
90
|
-
}) => void;
|
|
91
|
-
private onTokenExpired?: () => void;
|
|
92
|
-
|
|
93
|
-
constructor(options: TokenClientOptions = {}) {
|
|
94
|
-
this.storage = options.storage || new LocalStorageTokenStorage();
|
|
95
|
-
this.refreshEndpoint = options.refreshEndpoint;
|
|
96
|
-
this.onTokenRefresh = options.onTokenRefresh;
|
|
97
|
-
this.onTokenExpired = options.onTokenExpired;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
async getAccessToken(): Promise<string | null> {
|
|
101
|
-
return await this.storage.getAccessToken();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async getRefreshToken(): Promise<string | null> {
|
|
105
|
-
return await this.storage.getRefreshToken();
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
async setTokens(
|
|
109
|
-
accessToken: string,
|
|
110
|
-
refreshToken?: string,
|
|
111
|
-
accessTtl?: number,
|
|
112
|
-
refreshTtl?: number,
|
|
113
|
-
): Promise<void> {
|
|
114
|
-
await this.storage.setAccessToken(accessToken, accessTtl);
|
|
115
|
-
if (refreshToken) {
|
|
116
|
-
await this.storage.setRefreshToken(refreshToken, refreshTtl);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async clearTokens(): Promise<void> {
|
|
121
|
-
await this.storage.clearTokens();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
isTokenExpired(token: string): boolean {
|
|
125
|
-
try {
|
|
126
|
-
const parts = token.split('.');
|
|
127
|
-
const payloadPart = parts[1];
|
|
128
|
-
if (!payloadPart) return true;
|
|
129
|
-
const payload = JSON.parse(atob(payloadPart));
|
|
130
|
-
const now = Math.floor(Date.now() / 1000);
|
|
131
|
-
return payload.exp < now;
|
|
132
|
-
} catch {
|
|
133
|
-
return true;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
getTokenExpiration(token: string): Date | null {
|
|
138
|
-
try {
|
|
139
|
-
const parts = token.split('.');
|
|
140
|
-
const payloadPart = parts[1];
|
|
141
|
-
if (!payloadPart) return null;
|
|
142
|
-
const payload = JSON.parse(atob(payloadPart));
|
|
143
|
-
return new Date(payload.exp * 1000);
|
|
144
|
-
} catch {
|
|
145
|
-
return null;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async refreshTokens(): Promise<boolean> {
|
|
150
|
-
const refreshToken = await this.getRefreshToken();
|
|
151
|
-
|
|
152
|
-
if (!refreshToken || !this.refreshEndpoint) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
try {
|
|
157
|
-
const response = await fetch(this.refreshEndpoint, {
|
|
158
|
-
method: 'POST',
|
|
159
|
-
headers: {
|
|
160
|
-
'Content-Type': 'application/json',
|
|
161
|
-
},
|
|
162
|
-
body: JSON.stringify({ refreshToken }),
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
if (!response.ok) {
|
|
166
|
-
throw new Error('Failed to refresh token');
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
const data = (await response.json()) as {
|
|
170
|
-
accessToken: string;
|
|
171
|
-
refreshToken: string;
|
|
172
|
-
};
|
|
173
|
-
const { accessToken, refreshToken: newRefreshToken } = data;
|
|
174
|
-
|
|
175
|
-
await this.setTokens(accessToken, newRefreshToken);
|
|
176
|
-
|
|
177
|
-
if (this.onTokenRefresh) {
|
|
178
|
-
this.onTokenRefresh({ accessToken, refreshToken: newRefreshToken });
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
return true;
|
|
182
|
-
} catch (_error) {
|
|
183
|
-
await this.clearTokens();
|
|
184
|
-
if (this.onTokenExpired) {
|
|
185
|
-
this.onTokenExpired();
|
|
186
|
-
}
|
|
187
|
-
return false;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async getValidAccessToken(): Promise<string | null> {
|
|
192
|
-
const accessToken = await this.getAccessToken();
|
|
193
|
-
|
|
194
|
-
if (!accessToken) {
|
|
195
|
-
return null;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
if (this.isTokenExpired(accessToken)) {
|
|
199
|
-
const refreshed = await this.refreshTokens();
|
|
200
|
-
if (!refreshed) {
|
|
201
|
-
return null;
|
|
202
|
-
}
|
|
203
|
-
return await this.getAccessToken();
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
return accessToken;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
async createAuthHeaders(): Promise<Record<string, string>> {
|
|
210
|
-
const token = await this.getAccessToken();
|
|
211
|
-
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
async createValidAuthHeaders(): Promise<Record<string, string>> {
|
|
215
|
-
const token = await this.getValidAccessToken();
|
|
216
|
-
return token ? { Authorization: `Bearer ${token}` } : {};
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export { CacheTokenStorage } from './cacheTokenStorage.js';
|
package/src/hono/jwt.ts
DELETED
|
@@ -1,142 +0,0 @@
|
|
|
1
|
-
import type { Context, MiddlewareHandler, Next } from 'hono';
|
|
2
|
-
import {
|
|
3
|
-
type JwtClaims,
|
|
4
|
-
type JwtConfig,
|
|
5
|
-
JwtVerifier,
|
|
6
|
-
type TokenExtractionOptions,
|
|
7
|
-
} from '../jwt';
|
|
8
|
-
|
|
9
|
-
export {
|
|
10
|
-
JwtVerifier,
|
|
11
|
-
type JwtClaims,
|
|
12
|
-
type JwtConfig,
|
|
13
|
-
type TokenExtractionOptions,
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
function extractToken(
|
|
17
|
-
c: Context,
|
|
18
|
-
options: TokenExtractionOptions = {},
|
|
19
|
-
): string | null {
|
|
20
|
-
const {
|
|
21
|
-
headerName = 'authorization',
|
|
22
|
-
cookieName,
|
|
23
|
-
tokenPrefix = 'Bearer ',
|
|
24
|
-
} = options;
|
|
25
|
-
|
|
26
|
-
const headerValue = c.req.header(headerName);
|
|
27
|
-
if (headerValue) {
|
|
28
|
-
if (tokenPrefix && headerValue.startsWith(tokenPrefix)) {
|
|
29
|
-
return headerValue.slice(tokenPrefix.length);
|
|
30
|
-
}
|
|
31
|
-
return headerValue;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (cookieName) {
|
|
35
|
-
const cookieHeader = c.req.header('cookie');
|
|
36
|
-
if (cookieHeader) {
|
|
37
|
-
const match = cookieHeader.match(new RegExp(`${cookieName}=([^;]+)`));
|
|
38
|
-
if (match?.[1]) {
|
|
39
|
-
return match[1];
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface JwtMiddlewareOptions<TClaims extends JwtClaims = JwtClaims> {
|
|
48
|
-
config: JwtConfig;
|
|
49
|
-
extraction?: TokenExtractionOptions;
|
|
50
|
-
contextKey?: string;
|
|
51
|
-
onError?: (c: Context, error: Error) => Response | Promise<Response>;
|
|
52
|
-
transformClaims?: (claims: JwtClaims) => TClaims;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* JWT Middleware for Hono
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```typescript
|
|
60
|
-
* const jwt = new JwtMiddleware({
|
|
61
|
-
* config: { secret: process.env.JWT_SECRET! },
|
|
62
|
-
* });
|
|
63
|
-
*
|
|
64
|
-
* app.use('/api/*', jwt.handler());
|
|
65
|
-
*
|
|
66
|
-
* // Or for optional auth
|
|
67
|
-
* app.use('/public/*', jwt.optional());
|
|
68
|
-
* ```
|
|
69
|
-
*/
|
|
70
|
-
export class JwtMiddleware<TClaims extends JwtClaims = JwtClaims> {
|
|
71
|
-
private readonly verifier: JwtVerifier<TClaims>;
|
|
72
|
-
private readonly extraction: TokenExtractionOptions;
|
|
73
|
-
private readonly contextKey: string;
|
|
74
|
-
private readonly onError?: (
|
|
75
|
-
c: Context,
|
|
76
|
-
error: Error,
|
|
77
|
-
) => Response | Promise<Response>;
|
|
78
|
-
private readonly transformClaims?: (claims: JwtClaims) => TClaims;
|
|
79
|
-
|
|
80
|
-
constructor(options: JwtMiddlewareOptions<TClaims>) {
|
|
81
|
-
this.verifier = new JwtVerifier(options.config);
|
|
82
|
-
this.extraction = options.extraction ?? {};
|
|
83
|
-
this.contextKey = options.contextKey ?? 'jwtClaims';
|
|
84
|
-
this.onError = options.onError;
|
|
85
|
-
this.transformClaims = options.transformClaims;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Returns middleware that requires valid JWT
|
|
90
|
-
*/
|
|
91
|
-
handler(): MiddlewareHandler {
|
|
92
|
-
return async (c: Context, next: Next) => {
|
|
93
|
-
const token = extractToken(c, this.extraction);
|
|
94
|
-
|
|
95
|
-
if (!token) {
|
|
96
|
-
if (this.onError) {
|
|
97
|
-
return this.onError(c, new Error('No token provided'));
|
|
98
|
-
}
|
|
99
|
-
return c.json({ error: 'Unauthorized' }, 401);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
try {
|
|
103
|
-
const payload = await this.verifier.verify(token);
|
|
104
|
-
const claims = this.transformClaims
|
|
105
|
-
? this.transformClaims(payload)
|
|
106
|
-
: payload;
|
|
107
|
-
|
|
108
|
-
c.set(this.contextKey, claims);
|
|
109
|
-
c.set('jwtToken', token);
|
|
110
|
-
|
|
111
|
-
await next();
|
|
112
|
-
} catch (error) {
|
|
113
|
-
if (this.onError) {
|
|
114
|
-
return this.onError(c, error as Error);
|
|
115
|
-
}
|
|
116
|
-
return c.json({ error: 'Invalid token' }, 401);
|
|
117
|
-
}
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Returns middleware that allows unauthenticated requests
|
|
123
|
-
*/
|
|
124
|
-
optional(): MiddlewareHandler {
|
|
125
|
-
return async (c: Context, next: Next) => {
|
|
126
|
-
const token = extractToken(c, this.extraction);
|
|
127
|
-
|
|
128
|
-
if (token) {
|
|
129
|
-
const claims = await this.verifier.verifyOrNull(token);
|
|
130
|
-
if (claims) {
|
|
131
|
-
const transformed = this.transformClaims
|
|
132
|
-
? this.transformClaims(claims)
|
|
133
|
-
: claims;
|
|
134
|
-
c.set(this.contextKey, transformed);
|
|
135
|
-
c.set('jwtToken', token);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
await next();
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
}
|
package/src/hono/oidc.ts
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import type { Context, MiddlewareHandler, Next } from 'hono';
|
|
2
|
-
import {
|
|
3
|
-
type OidcClaims,
|
|
4
|
-
type OidcConfig,
|
|
5
|
-
type OidcUserInfo,
|
|
6
|
-
OidcVerifier,
|
|
7
|
-
type TokenExtractionOptions,
|
|
8
|
-
} from '../oidc';
|
|
9
|
-
|
|
10
|
-
export {
|
|
11
|
-
OidcVerifier,
|
|
12
|
-
type OidcClaims,
|
|
13
|
-
type OidcConfig,
|
|
14
|
-
type OidcUserInfo,
|
|
15
|
-
type TokenExtractionOptions,
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
function extractToken(
|
|
19
|
-
c: Context,
|
|
20
|
-
options: TokenExtractionOptions = {},
|
|
21
|
-
): string | null {
|
|
22
|
-
const {
|
|
23
|
-
headerName = 'authorization',
|
|
24
|
-
cookieName,
|
|
25
|
-
tokenPrefix = 'Bearer ',
|
|
26
|
-
} = options;
|
|
27
|
-
|
|
28
|
-
const headerValue = c.req.header(headerName);
|
|
29
|
-
if (headerValue) {
|
|
30
|
-
if (tokenPrefix && headerValue.startsWith(tokenPrefix)) {
|
|
31
|
-
return headerValue.slice(tokenPrefix.length);
|
|
32
|
-
}
|
|
33
|
-
return headerValue;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (cookieName) {
|
|
37
|
-
const cookieHeader = c.req.header('cookie');
|
|
38
|
-
if (cookieHeader) {
|
|
39
|
-
const match = cookieHeader.match(new RegExp(`${cookieName}=([^;]+)`));
|
|
40
|
-
if (match?.[1]) {
|
|
41
|
-
return match[1];
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
return null;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface OidcMiddlewareOptions<
|
|
50
|
-
TClaims extends OidcClaims = OidcClaims,
|
|
51
|
-
TUserInfo extends OidcUserInfo = OidcUserInfo,
|
|
52
|
-
> {
|
|
53
|
-
config: OidcConfig;
|
|
54
|
-
extraction?: TokenExtractionOptions;
|
|
55
|
-
contextKey?: string;
|
|
56
|
-
fetchUserInfo?: boolean;
|
|
57
|
-
onError?: (c: Context, error: Error) => Response | Promise<Response>;
|
|
58
|
-
transformClaims?: (claims: OidcClaims, userInfo?: TUserInfo) => TClaims;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* OIDC Middleware for Hono
|
|
63
|
-
*
|
|
64
|
-
* @example
|
|
65
|
-
* ```typescript
|
|
66
|
-
* const oidc = new OidcMiddleware({
|
|
67
|
-
* config: {
|
|
68
|
-
* issuer: 'https://auth.example.com',
|
|
69
|
-
* audience: 'my-client-id',
|
|
70
|
-
* },
|
|
71
|
-
* });
|
|
72
|
-
*
|
|
73
|
-
* app.use('/api/*', oidc.handler());
|
|
74
|
-
*
|
|
75
|
-
* // With user info
|
|
76
|
-
* const oidcWithUserInfo = new OidcMiddleware({
|
|
77
|
-
* config: { issuer: '...', audience: '...' },
|
|
78
|
-
* fetchUserInfo: true,
|
|
79
|
-
* });
|
|
80
|
-
* ```
|
|
81
|
-
*/
|
|
82
|
-
export class OidcMiddleware<
|
|
83
|
-
TClaims extends OidcClaims = OidcClaims,
|
|
84
|
-
TUserInfo extends OidcUserInfo = OidcUserInfo,
|
|
85
|
-
> {
|
|
86
|
-
private readonly verifier: OidcVerifier<TClaims, TUserInfo>;
|
|
87
|
-
private readonly extraction: TokenExtractionOptions;
|
|
88
|
-
private readonly contextKey: string;
|
|
89
|
-
private readonly fetchUserInfo: boolean;
|
|
90
|
-
private readonly onError?: (
|
|
91
|
-
c: Context,
|
|
92
|
-
error: Error,
|
|
93
|
-
) => Response | Promise<Response>;
|
|
94
|
-
private readonly transformClaims?: (
|
|
95
|
-
claims: OidcClaims,
|
|
96
|
-
userInfo?: TUserInfo,
|
|
97
|
-
) => TClaims;
|
|
98
|
-
|
|
99
|
-
constructor(options: OidcMiddlewareOptions<TClaims, TUserInfo>) {
|
|
100
|
-
this.verifier = new OidcVerifier(options.config);
|
|
101
|
-
this.extraction = options.extraction ?? {};
|
|
102
|
-
this.contextKey = options.contextKey ?? 'oidcClaims';
|
|
103
|
-
this.fetchUserInfo = options.fetchUserInfo ?? false;
|
|
104
|
-
this.onError = options.onError;
|
|
105
|
-
this.transformClaims = options.transformClaims;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
handler(): MiddlewareHandler {
|
|
109
|
-
return async (c: Context, next: Next) => {
|
|
110
|
-
const token = extractToken(c, this.extraction);
|
|
111
|
-
|
|
112
|
-
if (!token) {
|
|
113
|
-
if (this.onError) {
|
|
114
|
-
return this.onError(c, new Error('No token provided'));
|
|
115
|
-
}
|
|
116
|
-
return c.json({ error: 'Unauthorized' }, 401);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
const payload = await this.verifier.verify(token);
|
|
121
|
-
|
|
122
|
-
let userInfo: TUserInfo | undefined;
|
|
123
|
-
if (this.fetchUserInfo) {
|
|
124
|
-
userInfo = (await this.verifier.fetchUserInfo(token)) ?? undefined;
|
|
125
|
-
c.set('oidcUserInfo', userInfo);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
const claims = this.transformClaims
|
|
129
|
-
? this.transformClaims(payload, userInfo)
|
|
130
|
-
: payload;
|
|
131
|
-
|
|
132
|
-
c.set(this.contextKey, claims);
|
|
133
|
-
c.set('oidcToken', token);
|
|
134
|
-
|
|
135
|
-
await next();
|
|
136
|
-
} catch (error) {
|
|
137
|
-
if (this.onError) {
|
|
138
|
-
return this.onError(c, error as Error);
|
|
139
|
-
}
|
|
140
|
-
return c.json({ error: 'Invalid token' }, 401);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
optional(): MiddlewareHandler {
|
|
146
|
-
return async (c: Context, next: Next) => {
|
|
147
|
-
const token = extractToken(c, this.extraction);
|
|
148
|
-
|
|
149
|
-
if (token) {
|
|
150
|
-
const payload = await this.verifier.verifyOrNull(token);
|
|
151
|
-
if (payload) {
|
|
152
|
-
let userInfo: TUserInfo | undefined;
|
|
153
|
-
if (this.fetchUserInfo) {
|
|
154
|
-
userInfo = (await this.verifier.fetchUserInfo(token)) ?? undefined;
|
|
155
|
-
c.set('oidcUserInfo', userInfo);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const claims = this.transformClaims
|
|
159
|
-
? this.transformClaims(payload, userInfo)
|
|
160
|
-
: payload;
|
|
161
|
-
|
|
162
|
-
c.set(this.contextKey, claims);
|
|
163
|
-
c.set('oidcToken', token);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
await next();
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
}
|
package/src/index.ts
DELETED
package/src/jwt.ts
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import * as jose from 'jose';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Standard JWT claims
|
|
5
|
-
*/
|
|
6
|
-
export interface JwtClaims {
|
|
7
|
-
sub?: string;
|
|
8
|
-
iss?: string;
|
|
9
|
-
aud?: string | string[];
|
|
10
|
-
exp?: number;
|
|
11
|
-
iat?: number;
|
|
12
|
-
nbf?: number;
|
|
13
|
-
jti?: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Configuration for JWT verification with a symmetric secret
|
|
18
|
-
*/
|
|
19
|
-
export interface JwtSecretConfig {
|
|
20
|
-
secret: string;
|
|
21
|
-
issuer?: string;
|
|
22
|
-
audience?: string | string[];
|
|
23
|
-
algorithms?: string[];
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Configuration for JWT verification with JWKS
|
|
28
|
-
*/
|
|
29
|
-
export interface JwtJwksConfig {
|
|
30
|
-
jwksUri: string;
|
|
31
|
-
issuer?: string;
|
|
32
|
-
audience?: string | string[];
|
|
33
|
-
algorithms?: string[];
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export type JwtConfig = JwtSecretConfig | JwtJwksConfig;
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Options for token extraction from requests
|
|
40
|
-
*/
|
|
41
|
-
export interface TokenExtractionOptions {
|
|
42
|
-
headerName?: string;
|
|
43
|
-
cookieName?: string;
|
|
44
|
-
tokenPrefix?: string;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function isJwksConfig(config: JwtConfig): config is JwtJwksConfig {
|
|
48
|
-
return 'jwksUri' in config;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* JWT Verifier with JWKS caching
|
|
53
|
-
*/
|
|
54
|
-
export class JwtVerifier<TClaims extends JwtClaims = JwtClaims> {
|
|
55
|
-
private jwks: jose.JWTVerifyGetKey | null = null;
|
|
56
|
-
private readonly verifyOptions: jose.JWTVerifyOptions;
|
|
57
|
-
|
|
58
|
-
constructor(private readonly config: JwtConfig) {
|
|
59
|
-
this.verifyOptions = {
|
|
60
|
-
issuer: config.issuer,
|
|
61
|
-
audience: config.audience,
|
|
62
|
-
algorithms:
|
|
63
|
-
config.algorithms ?? (isJwksConfig(config) ? undefined : ['HS256']),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async verify(token: string): Promise<TClaims> {
|
|
68
|
-
if (isJwksConfig(this.config)) {
|
|
69
|
-
if (!this.jwks) {
|
|
70
|
-
this.jwks = jose.createRemoteJWKSet(new URL(this.config.jwksUri));
|
|
71
|
-
}
|
|
72
|
-
const { payload } = await jose.jwtVerify(
|
|
73
|
-
token,
|
|
74
|
-
this.jwks,
|
|
75
|
-
this.verifyOptions,
|
|
76
|
-
);
|
|
77
|
-
return payload as unknown as TClaims;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const secret = new TextEncoder().encode(this.config.secret);
|
|
81
|
-
const { payload } = await jose.jwtVerify(token, secret, this.verifyOptions);
|
|
82
|
-
return payload as unknown as TClaims;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async verifyOrNull(token: string): Promise<TClaims | null> {
|
|
86
|
-
try {
|
|
87
|
-
return await this.verify(token);
|
|
88
|
-
} catch {
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
clearCache(): void {
|
|
94
|
-
this.jwks = null;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/**
|
|
99
|
-
* Decode a JWT without verification
|
|
100
|
-
* WARNING: This does NOT verify the token signature!
|
|
101
|
-
*/
|
|
102
|
-
export function decodeJwt<TClaims extends JwtClaims = JwtClaims>(
|
|
103
|
-
token: string,
|
|
104
|
-
): TClaims {
|
|
105
|
-
return jose.decodeJwt(token) as unknown as TClaims;
|
|
106
|
-
}
|