@dotpress/auth 1.1.2 → 1.2.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/README.md +211 -35
- package/dist/__tests__/functional/cookies.test.d.ts +1 -0
- package/dist/__tests__/functional/cookies.test.js +386 -0
- package/dist/__tests__/functional/handlers.test.js +4 -0
- package/dist/__tests__/unit/cookies.test.d.ts +1 -0
- package/dist/__tests__/unit/cookies.test.js +466 -0
- package/dist/index.js +4 -0
- package/dist/lib/cookies.d.ts +42 -0
- package/dist/lib/cookies.js +136 -0
- package/dist/lib/handlers.js +40 -9
- package/dist/lib/middleware.js +2 -3
- package/dist/types/types.d.ts +20 -0
- package/package.json +3 -1
package/dist/lib/handlers.js
CHANGED
|
@@ -3,6 +3,7 @@ import bcrypt from 'bcryptjs';
|
|
|
3
3
|
import { getSettings } from './settings.js';
|
|
4
4
|
import { getRequestIp } from './ip.js';
|
|
5
5
|
import { generateTokensForUser, generateTokensFromRefreshToken, } from './tokens.js';
|
|
6
|
+
import { setAuthCookies, clearAuthCookies, extractTokensFromRequest, calculateExpiration, } from './cookies.js';
|
|
6
7
|
export const registerHandlers = (plugin) => {
|
|
7
8
|
const authGroup = plugin.addGroup('/auth');
|
|
8
9
|
const settings = getSettings();
|
|
@@ -15,15 +16,18 @@ export const registerHandlers = (plugin) => {
|
|
|
15
16
|
password: z.string(),
|
|
16
17
|
client: z.string().optional(),
|
|
17
18
|
aud: settings.aud?.required ? z.string() : z.string().optional(),
|
|
19
|
+
mode: z.enum(['body', 'cookie']).optional(),
|
|
18
20
|
}),
|
|
19
21
|
response: z.object({
|
|
20
22
|
userId: z.string(),
|
|
21
23
|
accessToken: z.string(),
|
|
22
24
|
refreshToken: z.string(),
|
|
25
|
+
expiresIn: z.number(),
|
|
26
|
+
expiresAt: z.string(),
|
|
23
27
|
}),
|
|
24
28
|
}),
|
|
25
|
-
handler: async ({ req }) => {
|
|
26
|
-
const { username, password, client, aud } = req.body;
|
|
29
|
+
handler: async ({ req, res }) => {
|
|
30
|
+
const { username, password, client, aud, mode } = req.body;
|
|
27
31
|
const ipAddress = getRequestIp(req);
|
|
28
32
|
const user = await settings.dataProvider.findUserIdentifiers(username);
|
|
29
33
|
if (!user) {
|
|
@@ -76,10 +80,15 @@ export const registerHandlers = (plugin) => {
|
|
|
76
80
|
ipAddress: ipAddress || '',
|
|
77
81
|
});
|
|
78
82
|
}
|
|
83
|
+
if (mode === 'cookie') {
|
|
84
|
+
setAuthCookies(res, { accessToken, refreshToken }, settings.accessTokenTTL);
|
|
85
|
+
}
|
|
86
|
+
const expiration = calculateExpiration(settings.accessTokenTTL);
|
|
79
87
|
return {
|
|
80
88
|
userId: user.userId,
|
|
81
89
|
accessToken,
|
|
82
90
|
refreshToken,
|
|
91
|
+
...expiration,
|
|
83
92
|
};
|
|
84
93
|
},
|
|
85
94
|
});
|
|
@@ -87,27 +96,48 @@ export const registerHandlers = (plugin) => {
|
|
|
87
96
|
method: 'post',
|
|
88
97
|
path: '/refresh',
|
|
89
98
|
schema: (z) => ({
|
|
90
|
-
body: z
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
99
|
+
body: z
|
|
100
|
+
.object({
|
|
101
|
+
accessToken: z.string().optional(),
|
|
102
|
+
refreshToken: z.string().optional(),
|
|
103
|
+
})
|
|
104
|
+
.optional(),
|
|
94
105
|
response: z.object({
|
|
95
106
|
userId: z.string(),
|
|
96
107
|
accessToken: z.string(),
|
|
97
108
|
refreshToken: z.string(),
|
|
109
|
+
expiresIn: z.number(),
|
|
110
|
+
expiresAt: z.string(),
|
|
98
111
|
}),
|
|
99
112
|
}),
|
|
100
|
-
handler: async ({ req }) => {
|
|
101
|
-
|
|
113
|
+
handler: async ({ req, res }) => {
|
|
114
|
+
// Extract tokens from cookies or body (auto-detect)
|
|
115
|
+
const extractedTokens = extractTokensFromRequest(req);
|
|
116
|
+
if (!extractedTokens) {
|
|
117
|
+
return unauthorizedError('NO_TOKENS_PROVIDED');
|
|
118
|
+
}
|
|
119
|
+
const { accessToken, refreshToken } = extractedTokens;
|
|
102
120
|
try {
|
|
103
121
|
const tokens = await generateTokensFromRefreshToken({
|
|
104
122
|
accessToken,
|
|
105
123
|
refreshToken,
|
|
106
124
|
});
|
|
125
|
+
// Set cookies if the request came with cookies (auto-detect mode)
|
|
126
|
+
const mode = req.cookies?.[settings.cookies?.refreshTokenName || 'dotpress_refresh_token']
|
|
127
|
+
? 'cookie'
|
|
128
|
+
: 'body';
|
|
129
|
+
if (mode === 'cookie') {
|
|
130
|
+
setAuthCookies(res, {
|
|
131
|
+
accessToken: tokens.accessToken,
|
|
132
|
+
refreshToken: tokens.refreshToken,
|
|
133
|
+
}, settings.accessTokenTTL);
|
|
134
|
+
}
|
|
135
|
+
const expiration = calculateExpiration(settings.accessTokenTTL);
|
|
107
136
|
return {
|
|
108
137
|
userId: tokens.userId,
|
|
109
138
|
accessToken: tokens.accessToken,
|
|
110
139
|
refreshToken: tokens.refreshToken,
|
|
140
|
+
...expiration,
|
|
111
141
|
};
|
|
112
142
|
}
|
|
113
143
|
catch (e) {
|
|
@@ -135,12 +165,13 @@ export const registerHandlers = (plugin) => {
|
|
|
135
165
|
authGroup.defineRoute({
|
|
136
166
|
method: 'post',
|
|
137
167
|
path: '/logout',
|
|
138
|
-
handler: async ({ req }) => {
|
|
168
|
+
handler: async ({ req, res }) => {
|
|
139
169
|
const userId = req.user?.id || '';
|
|
140
170
|
if (!userId) {
|
|
141
171
|
return unauthorizedError();
|
|
142
172
|
}
|
|
143
173
|
await settings.dataProvider.revokeTokens(userId);
|
|
174
|
+
clearAuthCookies(res);
|
|
144
175
|
return { success: true };
|
|
145
176
|
},
|
|
146
177
|
});
|
package/dist/lib/middleware.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { getSettings } from './settings.js';
|
|
2
2
|
import { validateAndDecodeAccessToken } from './tokens.js';
|
|
3
|
+
import { extractAccessToken } from './cookies.js';
|
|
3
4
|
export const attachAuthenticatedUser = async (req, _res, next) => {
|
|
4
|
-
const accessToken = req
|
|
5
|
-
? req.headers.authorization.replace('Bearer ', '')
|
|
6
|
-
: '';
|
|
5
|
+
const accessToken = extractAccessToken(req);
|
|
7
6
|
if (!accessToken) {
|
|
8
7
|
next();
|
|
9
8
|
return;
|
package/dist/types/types.d.ts
CHANGED
|
@@ -10,10 +10,23 @@ export type KeyPairSettings = {
|
|
|
10
10
|
secretKey: string;
|
|
11
11
|
publicKey: string;
|
|
12
12
|
};
|
|
13
|
+
export type CookieSettings = {
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
httpOnly?: boolean;
|
|
16
|
+
secure?: boolean;
|
|
17
|
+
sameSite?: 'strict' | 'lax' | 'none';
|
|
18
|
+
domain?: string;
|
|
19
|
+
path?: string;
|
|
20
|
+
accessTokenName?: string;
|
|
21
|
+
refreshTokenName?: string;
|
|
22
|
+
accessTokenInCookie?: boolean;
|
|
23
|
+
};
|
|
24
|
+
export type AuthMode = 'body' | 'cookie';
|
|
13
25
|
export type AuthSettings = {
|
|
14
26
|
accessTokenTTL: number;
|
|
15
27
|
refreshTokenTTLInDays: number;
|
|
16
28
|
issuer: string;
|
|
29
|
+
cookies?: CookieSettings;
|
|
17
30
|
onLoginFailed?: (e: LoginFailedEvent) => Promise<void>;
|
|
18
31
|
onLoginSuccess?: (e: LoginSuccessEvent) => Promise<void>;
|
|
19
32
|
aud?: {
|
|
@@ -45,6 +58,13 @@ export type TokenPair = {
|
|
|
45
58
|
accessToken: string;
|
|
46
59
|
refreshToken: string;
|
|
47
60
|
};
|
|
61
|
+
export type TokenResponse = {
|
|
62
|
+
userId: string;
|
|
63
|
+
accessToken: string;
|
|
64
|
+
refreshToken: string;
|
|
65
|
+
expiresIn: number;
|
|
66
|
+
expiresAt: string;
|
|
67
|
+
};
|
|
48
68
|
export type CreateTokenPayload = {
|
|
49
69
|
sub: string;
|
|
50
70
|
username: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dotpress/auth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "JWT Authentication plugin for Dotpress library",
|
|
5
5
|
"author": "Renaud Guyon",
|
|
6
6
|
"license": "MIT",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"test:watch": "vitest watch"
|
|
32
32
|
},
|
|
33
33
|
"peerDependencies": {
|
|
34
|
+
"cookie-parser": "^1.4.7",
|
|
34
35
|
"dotpress": "^1",
|
|
35
36
|
"zod": "^3"
|
|
36
37
|
},
|
|
@@ -42,6 +43,7 @@
|
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@eslint/js": "^9.24.0",
|
|
44
45
|
"@types/bcrypt": "^5.0.2",
|
|
46
|
+
"@types/cookie-parser": "^1.4.7",
|
|
45
47
|
"@types/express": "^4.17.21",
|
|
46
48
|
"@types/jsonwebtoken": "^9.0.9",
|
|
47
49
|
"@types/request-ip": "^0.0.41",
|