@dotpress/auth 1.3.1 → 2.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.
@@ -1,42 +0,0 @@
1
- import type { Response, Request } from 'express';
2
- import type { CookieSettings, AuthMode } from '../types/types.js';
3
- type ResolvedCookieSettings = Required<Omit<CookieSettings, 'domain'>> & {
4
- domain?: string;
5
- };
6
- export declare const getCookieSettings: () => ResolvedCookieSettings;
7
- /**
8
- * Set authentication cookies on the response
9
- */
10
- export declare const setAuthCookies: (res: Response, tokens: {
11
- accessToken: string;
12
- refreshToken: string;
13
- }, expiresInSeconds: number) => void;
14
- /**
15
- * Clear authentication cookies
16
- */
17
- export declare const clearAuthCookies: (res: Response) => void;
18
- /**
19
- * Detect authentication mode from request
20
- * Priority: cookies first, then body
21
- */
22
- export declare const detectAuthMode: (req: Request) => AuthMode | null;
23
- /**
24
- * Extract tokens from request based on detected mode
25
- */
26
- export declare const extractTokensFromRequest: (req: Request) => {
27
- accessToken: string;
28
- refreshToken: string;
29
- } | null;
30
- /**
31
- * Extract access token from request
32
- * Priority: Authorization header > cookie > body
33
- */
34
- export declare const extractAccessToken: (req: Request) => string | null;
35
- /**
36
- * Calculate expiration timestamp
37
- */
38
- export declare const calculateExpiration: (expiresInSeconds: number) => {
39
- expiresIn: number;
40
- expiresAt: string;
41
- };
42
- export {};
@@ -1,136 +0,0 @@
1
- import { getSettings } from './settings.js';
2
- const DEFAULT_COOKIE_SETTINGS = {
3
- enabled: false,
4
- httpOnly: true,
5
- secure: process.env.NODE_ENV === 'production',
6
- sameSite: 'lax',
7
- path: '/',
8
- accessTokenName: 'dotpress_access_token',
9
- refreshTokenName: 'dotpress_refresh_token',
10
- accessTokenInCookie: false,
11
- domain: undefined,
12
- };
13
- export const getCookieSettings = () => {
14
- const settings = getSettings();
15
- return {
16
- ...DEFAULT_COOKIE_SETTINGS,
17
- ...settings.cookies,
18
- };
19
- };
20
- /**
21
- * Set authentication cookies on the response
22
- */
23
- export const setAuthCookies = (res, tokens, expiresInSeconds) => {
24
- const cookieSettings = getCookieSettings();
25
- if (!cookieSettings.enabled) {
26
- return;
27
- }
28
- const cookieOptions = {
29
- httpOnly: cookieSettings.httpOnly,
30
- secure: cookieSettings.secure,
31
- sameSite: cookieSettings.sameSite,
32
- path: cookieSettings.path,
33
- domain: cookieSettings.domain,
34
- maxAge: expiresInSeconds * 1000,
35
- };
36
- // Always set refresh token in cookie for 'cookie' mode
37
- res.cookie(cookieSettings.refreshTokenName, tokens.refreshToken, cookieOptions);
38
- // Optionally set access token in cookie
39
- if (cookieSettings.accessTokenInCookie) {
40
- res.cookie(cookieSettings.accessTokenName, tokens.accessToken, cookieOptions);
41
- }
42
- };
43
- /**
44
- * Clear authentication cookies
45
- */
46
- export const clearAuthCookies = (res) => {
47
- const cookieSettings = getCookieSettings();
48
- if (!cookieSettings.enabled) {
49
- return;
50
- }
51
- const cookieOptions = {
52
- httpOnly: cookieSettings.httpOnly,
53
- secure: cookieSettings.secure,
54
- sameSite: cookieSettings.sameSite,
55
- path: cookieSettings.path,
56
- domain: cookieSettings.domain,
57
- };
58
- res.clearCookie(cookieSettings.refreshTokenName, cookieOptions);
59
- if (cookieSettings.accessTokenInCookie) {
60
- res.clearCookie(cookieSettings.accessTokenName, cookieOptions);
61
- }
62
- };
63
- /**
64
- * Detect authentication mode from request
65
- * Priority: cookies first, then body
66
- */
67
- export const detectAuthMode = (req) => {
68
- const cookieSettings = getCookieSettings();
69
- // Check if refresh token exists in cookies
70
- if (req.cookies?.[cookieSettings.refreshTokenName]) {
71
- return 'cookie';
72
- }
73
- // Check if refresh token exists in body
74
- if (req.body?.refreshToken) {
75
- return 'body';
76
- }
77
- return null;
78
- };
79
- /**
80
- * Extract tokens from request based on detected mode
81
- */
82
- export const extractTokensFromRequest = (req) => {
83
- const mode = detectAuthMode(req);
84
- const cookieSettings = getCookieSettings();
85
- if (mode === 'cookie') {
86
- const refreshToken = req.cookies?.[cookieSettings.refreshTokenName];
87
- const accessToken = cookieSettings.accessTokenInCookie
88
- ? req.cookies?.[cookieSettings.accessTokenName]
89
- : req.body?.accessToken;
90
- if (!refreshToken || !accessToken) {
91
- return null;
92
- }
93
- return { accessToken, refreshToken };
94
- }
95
- if (mode === 'body') {
96
- const { accessToken, refreshToken } = req.body || {};
97
- if (!accessToken || !refreshToken) {
98
- return null;
99
- }
100
- return { accessToken, refreshToken };
101
- }
102
- return null;
103
- };
104
- /**
105
- * Extract access token from request
106
- * Priority: Authorization header > cookie > body
107
- */
108
- export const extractAccessToken = (req) => {
109
- // Priority 1: Authorization header
110
- const authHeader = req.headers.authorization;
111
- if (authHeader?.startsWith('Bearer ')) {
112
- return authHeader.replace('Bearer ', '');
113
- }
114
- const cookieSettings = getCookieSettings();
115
- // Priority 2: Cookie (if enabled)
116
- if (cookieSettings.enabled &&
117
- cookieSettings.accessTokenInCookie &&
118
- req.cookies?.[cookieSettings.accessTokenName]) {
119
- return req.cookies[cookieSettings.accessTokenName];
120
- }
121
- // Priority 3: Body
122
- if (req.body?.accessToken) {
123
- return req.body.accessToken;
124
- }
125
- return null;
126
- };
127
- /**
128
- * Calculate expiration timestamp
129
- */
130
- export const calculateExpiration = (expiresInSeconds) => {
131
- const expiresAt = new Date(Date.now() + expiresInSeconds * 1000);
132
- return {
133
- expiresIn: expiresInSeconds,
134
- expiresAt: expiresAt.toISOString(),
135
- };
136
- };
@@ -1 +0,0 @@
1
- export declare const generateUUID: () => `${string}-${string}-${string}-${string}-${string}`;
@@ -1,4 +0,0 @@
1
- import { randomUUID } from 'crypto';
2
- export const generateUUID = () => {
3
- return randomUUID();
4
- };
@@ -1,2 +0,0 @@
1
- import type { PluginAPI } from 'dotpress';
2
- export declare const registerHandlers: (plugin: PluginAPI) => void;
@@ -1,263 +0,0 @@
1
- import { unauthorizedError, internalError } from 'dotpress';
2
- import bcrypt from 'bcryptjs';
3
- import { getSettings } from './settings.js';
4
- import { getRequestIp } from './ip.js';
5
- import { generateTokensForUser, generateTokensFromRefreshToken, validateAndDecodeAccessToken, } from './tokens.js';
6
- import { setAuthCookies, clearAuthCookies, extractTokensFromRequest, calculateExpiration, getCookieSettings, } from './cookies.js';
7
- export const registerHandlers = (plugin) => {
8
- const authGroup = plugin.addGroup('/auth');
9
- const settings = getSettings();
10
- authGroup.defineRoute({
11
- method: 'post',
12
- path: '/login',
13
- schema: (z) => ({
14
- body: z.object({
15
- username: z.string(),
16
- password: z.string(),
17
- client: z.string().optional(),
18
- aud: settings.aud?.required ? z.string() : z.string().optional(),
19
- mode: z.enum(['body', 'cookie']).optional(),
20
- }),
21
- response: z.object({
22
- userId: z.string(),
23
- accessToken: z.string(),
24
- refreshToken: z.string(),
25
- expiresIn: z.number(),
26
- expiresAt: z.string(),
27
- }),
28
- }),
29
- handler: async ({ req, res }) => {
30
- const { username, password, client, aud, mode } = req.body;
31
- const ipAddress = getRequestIp(req);
32
- const user = await settings.dataProvider.findUserIdentifiers(username);
33
- if (!user) {
34
- if (settings.onLoginFailed) {
35
- await settings.onLoginFailed({
36
- username,
37
- errorType: 'invalid_username',
38
- client: client || '',
39
- ipAddress: ipAddress || '',
40
- });
41
- }
42
- return unauthorizedError('INVALID_LOGIN');
43
- }
44
- const isPasswordValid = await bcrypt.compare(password, user.passwordHash);
45
- if (!isPasswordValid) {
46
- if (settings.onLoginFailed) {
47
- await settings.onLoginFailed({
48
- username,
49
- errorType: 'invalid_password',
50
- client: client || '',
51
- ipAddress: ipAddress || '',
52
- });
53
- }
54
- return unauthorizedError('INVALID_LOGIN');
55
- }
56
- if (aud && typeof settings.aud?.isUserRoleAllowed === 'function') {
57
- const isAllowed = settings.aud.isUserRoleAllowed(user.role || '', aud);
58
- if (!isAllowed) {
59
- if (settings.onLoginFailed) {
60
- await settings.onLoginFailed({
61
- username,
62
- errorType: 'invalid_role',
63
- client: client || '',
64
- ipAddress: ipAddress || '',
65
- });
66
- }
67
- return unauthorizedError('INVALID_LOGIN');
68
- }
69
- }
70
- const { accessToken, refreshToken } = await generateTokensForUser({
71
- username,
72
- userId: user.userId,
73
- aud,
74
- });
75
- if (settings.onLoginSuccess) {
76
- await settings.onLoginSuccess({
77
- userId: user.userId,
78
- username,
79
- client: client || '',
80
- ipAddress: ipAddress || '',
81
- });
82
- }
83
- if (mode === 'cookie') {
84
- setAuthCookies(res, { accessToken, refreshToken }, settings.refreshTokenTTLInDays * 24 * 60 * 60);
85
- }
86
- const expiration = calculateExpiration(settings.accessTokenTTL);
87
- return {
88
- userId: user.userId,
89
- accessToken,
90
- refreshToken,
91
- ...expiration,
92
- };
93
- },
94
- });
95
- authGroup.defineRoute({
96
- method: 'post',
97
- path: '/refresh',
98
- schema: (z) => ({
99
- body: z
100
- .object({
101
- accessToken: z.string().optional(),
102
- refreshToken: z.string().optional(),
103
- })
104
- .optional(),
105
- response: z.object({
106
- userId: z.string(),
107
- accessToken: z.string(),
108
- refreshToken: z.string(),
109
- expiresIn: z.number(),
110
- expiresAt: z.string(),
111
- }),
112
- }),
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;
120
- try {
121
- const tokens = await generateTokensFromRefreshToken({
122
- accessToken,
123
- refreshToken,
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);
136
- return {
137
- userId: tokens.userId,
138
- accessToken: tokens.accessToken,
139
- refreshToken: tokens.refreshToken,
140
- ...expiration,
141
- };
142
- }
143
- catch (e) {
144
- const errorMessage = e.message;
145
- if (errorMessage === 'Invalid_Refresh_Token') {
146
- return unauthorizedError('INVALID_REFRESH_TOKEN');
147
- }
148
- if (errorMessage === 'Expired_Refresh_Token') {
149
- return unauthorizedError('EXPIRED_REFRESH_TOKEN');
150
- }
151
- return internalError('Can not refresh tokens.');
152
- }
153
- },
154
- });
155
- authGroup.defineRoute({
156
- method: 'get',
157
- path: '/me',
158
- handler: async ({ req }) => {
159
- if (!req.user) {
160
- return unauthorizedError();
161
- }
162
- return req.user;
163
- },
164
- });
165
- authGroup.defineRoute({
166
- method: 'post',
167
- path: '/restore',
168
- schema: (z) => ({
169
- response: z.object({
170
- userId: z.string(),
171
- accessToken: z.string(),
172
- refreshToken: z.string(),
173
- expiresIn: z.number(),
174
- expiresAt: z.string(),
175
- user: z.unknown(),
176
- }),
177
- }),
178
- handler: async ({ req, res }) => {
179
- const cookieSettings = getCookieSettings();
180
- // Only support cookie mode for restore
181
- if (!cookieSettings.enabled) {
182
- return unauthorizedError('COOKIES_NOT_ENABLED');
183
- }
184
- // This endpoint requires accessTokenInCookie to be enabled
185
- if (!cookieSettings.accessTokenInCookie) {
186
- return unauthorizedError('ACCESS_TOKEN_NOT_IN_COOKIE');
187
- }
188
- const refreshToken = req.cookies?.[cookieSettings.refreshTokenName];
189
- const accessToken = req.cookies?.[cookieSettings.accessTokenName];
190
- if (!refreshToken) {
191
- return unauthorizedError('NO_REFRESH_TOKEN');
192
- }
193
- if (!accessToken) {
194
- return unauthorizedError('NO_ACCESS_TOKEN');
195
- }
196
- // Try to validate access token first
197
- try {
198
- const { userId } = validateAndDecodeAccessToken(accessToken);
199
- const user = await settings.dataProvider.findUserById(userId);
200
- if (user) {
201
- const expiration = calculateExpiration(settings.accessTokenTTL);
202
- return {
203
- userId,
204
- accessToken,
205
- refreshToken,
206
- ...expiration,
207
- user,
208
- };
209
- }
210
- }
211
- catch {
212
- // Access token is invalid or expired, continue to refresh logic
213
- }
214
- // Access token is expired, try to refresh using refresh token
215
- try {
216
- const tokens = await generateTokensFromRefreshToken({
217
- accessToken,
218
- refreshToken,
219
- });
220
- // Set new cookies
221
- setAuthCookies(res, {
222
- accessToken: tokens.accessToken,
223
- refreshToken: tokens.refreshToken,
224
- }, settings.refreshTokenTTLInDays * 24 * 60 * 60);
225
- const user = await settings.dataProvider.findUserById(tokens.userId);
226
- if (!user) {
227
- return unauthorizedError('USER_NOT_FOUND');
228
- }
229
- const expiration = calculateExpiration(settings.accessTokenTTL);
230
- return {
231
- userId: tokens.userId,
232
- accessToken: tokens.accessToken,
233
- refreshToken: tokens.refreshToken,
234
- ...expiration,
235
- user,
236
- };
237
- }
238
- catch (e) {
239
- const errorMessage = e.message;
240
- if (errorMessage === 'Invalid_Refresh_Token') {
241
- return unauthorizedError('INVALID_REFRESH_TOKEN');
242
- }
243
- if (errorMessage === 'Expired_Refresh_Token') {
244
- return unauthorizedError('EXPIRED_REFRESH_TOKEN');
245
- }
246
- return internalError('Cannot restore session.');
247
- }
248
- },
249
- });
250
- authGroup.defineRoute({
251
- method: 'post',
252
- path: '/logout',
253
- handler: async ({ req, res }) => {
254
- const userId = req.user?.id || '';
255
- if (!userId) {
256
- return unauthorizedError();
257
- }
258
- await settings.dataProvider.revokeTokens(userId);
259
- clearAuthCookies(res);
260
- return { success: true };
261
- },
262
- });
263
- };
package/dist/lib/ip.d.ts DELETED
@@ -1,2 +0,0 @@
1
- import type { Request } from 'express';
2
- export declare const getRequestIp: (req: Request) => string | null;
package/dist/lib/ip.js DELETED
@@ -1,15 +0,0 @@
1
- import requestIp from 'request-ip';
2
- export const getRequestIp = (req) => {
3
- const clientIp = requestIp.getClientIp(req);
4
- const localIps = [
5
- '127.0.0.1',
6
- 'localhost',
7
- '::1',
8
- '::ffff',
9
- '::ffff:127.0.0.1',
10
- ];
11
- if (clientIp && localIps.includes(clientIp)) {
12
- return null;
13
- }
14
- return clientIp;
15
- };
@@ -1,2 +0,0 @@
1
- import type { Request, Response, NextFunction } from 'express';
2
- export declare const attachAuthenticatedUser: (req: Request, _res: Response, next: NextFunction) => Promise<void>;
@@ -1,23 +0,0 @@
1
- import { getSettings } from './settings.js';
2
- import { validateAndDecodeAccessToken } from './tokens.js';
3
- import { extractAccessToken } from './cookies.js';
4
- export const attachAuthenticatedUser = async (req, _res, next) => {
5
- const accessToken = extractAccessToken(req);
6
- if (!accessToken) {
7
- next();
8
- return;
9
- }
10
- try {
11
- const settings = getSettings();
12
- const { userId } = validateAndDecodeAccessToken(accessToken);
13
- const user = await settings.dataProvider.findUserById(userId);
14
- if (user) {
15
- req.user = user;
16
- }
17
- next();
18
- }
19
- catch (e) {
20
- console.warn(e);
21
- next();
22
- }
23
- };
@@ -1,3 +0,0 @@
1
- import { PluginSettings } from '../types/types.js';
2
- export declare const setSettings: (settings: Partial<PluginSettings>) => void;
3
- export declare const getSettings: () => PluginSettings;
@@ -1,42 +0,0 @@
1
- let pluginSettings = {
2
- accessTokenTTL: 3600,
3
- refreshTokenTTLInDays: 14,
4
- issuer: '',
5
- keys: {
6
- secretKey: '',
7
- publicKey: '',
8
- },
9
- dataProvider: {
10
- storeRefreshToken: () => {
11
- throw new Error('storeRefreshToken() is not implemented.');
12
- },
13
- findRefreshToken: () => {
14
- throw new Error('findRefreshToken() is not implemented.');
15
- },
16
- markRefreshTokenAsUsed: () => {
17
- throw new Error('markRefreshTokenAsUsed() is not implemented.');
18
- },
19
- revokeTokens: () => {
20
- throw new Error('revokeTokens() is not implemented.');
21
- },
22
- findUserById: () => {
23
- throw new Error('findUserById() is not implemented.');
24
- },
25
- findUserIdentifiers: () => {
26
- throw new Error('findUserByUsername() is not implemented.');
27
- },
28
- },
29
- onLoginFailed: () => {
30
- return Promise.resolve();
31
- },
32
- onLoginSuccess: () => {
33
- return Promise.resolve();
34
- },
35
- };
36
- export const setSettings = (settings) => {
37
- pluginSettings = {
38
- ...pluginSettings,
39
- ...settings,
40
- };
41
- };
42
- export const getSettings = () => pluginSettings;
@@ -1,38 +0,0 @@
1
- import { TokenPair } from '../types/types.js';
2
- type CreateTokenPayload = {
3
- sub: string;
4
- username: string;
5
- auth_time: number;
6
- aud?: string;
7
- };
8
- type CreatedAccessToken = {
9
- jwtId: string;
10
- accessToken: string;
11
- };
12
- type CreatedRefreshToken = {
13
- token: string;
14
- tokenHash: string;
15
- };
16
- type UserIdentifiers = {
17
- userId: string;
18
- username: string;
19
- aud?: string;
20
- };
21
- export declare const createAccessTokenFromPayload: (payload: CreateTokenPayload) => CreatedAccessToken;
22
- export declare const generateRefreshToken: () => CreatedRefreshToken;
23
- /**
24
- * Generate a pair of access and refresh tokens for a given user
25
- */
26
- export declare const generateTokensForUser: ({ userId, username, aud, }: UserIdentifiers) => Promise<TokenPair>;
27
- export declare const generateAccessToken: ({ userId, username, aud, }: UserIdentifiers) => CreatedAccessToken;
28
- /**
29
- * Regenerate a pair of token using a refresh token
30
- * Expired access token must be provided in order to validate userId and jwtId
31
- */
32
- export declare const generateTokensFromRefreshToken: ({ accessToken, refreshToken, }: TokenPair) => Promise<TokenPair & {
33
- userId: string;
34
- }>;
35
- export declare const validateAndDecodeAccessToken: (accessToken: string) => {
36
- userId: string;
37
- };
38
- export {};