@dotpress/auth 1.1.2 → 1.2.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.
@@ -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.refreshTokenTTLInDays * 24 * 60 * 60);
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.object({
91
- accessToken: z.string(),
92
- refreshToken: z.string(),
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
- const { accessToken, refreshToken } = req.body;
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.refreshTokenTTLInDays * 24 * 60 * 60);
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
  });
@@ -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.headers && req.headers.authorization
5
- ? req.headers.authorization.replace('Bearer ', '')
6
- : '';
5
+ const accessToken = extractAccessToken(req);
7
6
  if (!accessToken) {
8
7
  next();
9
8
  return;
@@ -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.1.2",
3
+ "version": "1.2.1",
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",