@devmoods/express-extras 0.58.0 → 0.60.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/auth/emailAndPassword.js +45 -35
- package/dist/auth/google.d.ts +13 -0
- package/dist/auth/google.js +94 -0
- package/dist/auth/passwordless.js +24 -20
- package/dist/auth/types.d.ts +3 -2
- package/dist/auth/types.js +1 -0
- package/dist/auth.d.ts +2 -1
- package/dist/auth.js +28 -24
- package/dist/cookie-consent.js +17 -16
- package/dist/manage.js +32 -0
- package/dist/router.d.ts +8 -8
- package/dist/routes.d.ts +3 -0
- package/dist/routes.js +43 -0
- package/dist/validate.d.ts +2 -1
- package/dist/vite/defineViteConfig.js +10 -9
- package/dist/vite/vitest.d.ts +1 -0
- package/dist/vite/vitest.js +1 -0
- package/package.json +33 -31
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import argon2 from 'argon2';
|
|
2
2
|
import bcrypt from 'bcrypt';
|
|
3
|
+
import {} from 'express';
|
|
3
4
|
import { createTokenResponse, tokenSchema } from './token.js';
|
|
4
5
|
import {} from './types.js';
|
|
5
6
|
import { Unauthorized } from '../errors.js';
|
|
6
7
|
import { getLogger } from '../logger.js';
|
|
7
|
-
import { route } from '../middleware.js';
|
|
8
8
|
import { startTimer } from '../utils.js';
|
|
9
|
-
import {
|
|
9
|
+
import { jsonSchema } from '../validate.js';
|
|
10
10
|
const passwordSchema = { type: 'string', minLength: 4 };
|
|
11
11
|
const emailSchema = { type: 'string', minLength: 4 };
|
|
12
12
|
const logger = getLogger('auth');
|
|
@@ -14,14 +14,16 @@ export function EmailAndPasswordAuthProvider(options) {
|
|
|
14
14
|
const skipPasswordCheck = async () => '';
|
|
15
15
|
return {
|
|
16
16
|
setupRoutes(router, context) {
|
|
17
|
-
router.post('/login',
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
17
|
+
router.post('/login', {
|
|
18
|
+
request: jsonSchema({
|
|
19
|
+
type: 'object',
|
|
20
|
+
properties: {
|
|
21
|
+
email: emailSchema,
|
|
22
|
+
password: passwordSchema,
|
|
23
|
+
},
|
|
24
|
+
required: ['email', 'password'],
|
|
25
|
+
}),
|
|
26
|
+
}, async (req, res) => {
|
|
25
27
|
const { email, password } = req.body;
|
|
26
28
|
const timer = startTimer();
|
|
27
29
|
try {
|
|
@@ -36,12 +38,14 @@ export function EmailAndPasswordAuthProvider(options) {
|
|
|
36
38
|
await context.jitter(timer.getDuration());
|
|
37
39
|
throw Unauthorized(error.message);
|
|
38
40
|
}
|
|
39
|
-
})
|
|
40
|
-
router.post('/change-password',
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
});
|
|
42
|
+
router.post('/change-password', {
|
|
43
|
+
request: jsonSchema({
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: { token: tokenSchema, password: passwordSchema },
|
|
46
|
+
required: ['token', 'password'],
|
|
47
|
+
}),
|
|
48
|
+
}, async (req) => {
|
|
45
49
|
const { password, token } = req.body;
|
|
46
50
|
const decoded = context.decodeTemporaryToken(token);
|
|
47
51
|
const user = await context.getUserById(decoded.payload.uid);
|
|
@@ -58,12 +62,14 @@ export function EmailAndPasswordAuthProvider(options) {
|
|
|
58
62
|
});
|
|
59
63
|
logger.info('user changed password');
|
|
60
64
|
return 200;
|
|
61
|
-
})
|
|
62
|
-
router.post('/verify-email-request',
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
});
|
|
66
|
+
router.post('/verify-email-request', {
|
|
67
|
+
request: jsonSchema({
|
|
68
|
+
type: 'object',
|
|
69
|
+
properties: { email: emailSchema },
|
|
70
|
+
required: ['email'],
|
|
71
|
+
}),
|
|
72
|
+
}, async (req) => {
|
|
67
73
|
const { email } = req.body;
|
|
68
74
|
const timer = startTimer();
|
|
69
75
|
const user = await options.login(email, skipPasswordCheck);
|
|
@@ -71,12 +77,14 @@ export function EmailAndPasswordAuthProvider(options) {
|
|
|
71
77
|
await options.onEmailVerificationRequest?.(user, temporaryToken);
|
|
72
78
|
await context.jitter(timer.getDuration());
|
|
73
79
|
return 202;
|
|
74
|
-
})
|
|
75
|
-
router.post('/verify-email',
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
+
});
|
|
81
|
+
router.post('/verify-email', {
|
|
82
|
+
request: jsonSchema({
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: { token: tokenSchema, email: emailSchema },
|
|
85
|
+
required: ['token', 'email'],
|
|
86
|
+
}),
|
|
87
|
+
}, async (req) => {
|
|
80
88
|
const { token, email } = req.body;
|
|
81
89
|
const user = await options.login(email, skipPasswordCheck);
|
|
82
90
|
try {
|
|
@@ -94,12 +102,14 @@ export function EmailAndPasswordAuthProvider(options) {
|
|
|
94
102
|
await options.onEmailVerified?.(user);
|
|
95
103
|
logger.info('user verified');
|
|
96
104
|
return 200;
|
|
97
|
-
})
|
|
98
|
-
router.post('/reset',
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
105
|
+
});
|
|
106
|
+
router.post('/reset', {
|
|
107
|
+
request: jsonSchema({
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: { email: emailSchema },
|
|
110
|
+
required: ['email'],
|
|
111
|
+
}),
|
|
112
|
+
}, context.middleware(false), async (req) => {
|
|
103
113
|
const { email } = req.body;
|
|
104
114
|
const timer = startTimer();
|
|
105
115
|
try {
|
|
@@ -113,7 +123,7 @@ export function EmailAndPasswordAuthProvider(options) {
|
|
|
113
123
|
logger.error(error, 'auth reset failed');
|
|
114
124
|
}
|
|
115
125
|
return 202;
|
|
116
|
-
})
|
|
126
|
+
});
|
|
117
127
|
},
|
|
118
128
|
};
|
|
119
129
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type AnyUser, type AuthProvider } from './types.js';
|
|
2
|
+
interface GoogleProfile {
|
|
3
|
+
email: string;
|
|
4
|
+
fullName: string;
|
|
5
|
+
}
|
|
6
|
+
interface GoogleAuthProviderOptions<UserModel = AnyUser> {
|
|
7
|
+
clientId: string;
|
|
8
|
+
clientSecret: string;
|
|
9
|
+
redirectUri: string;
|
|
10
|
+
getOrCreateUserFromUserIdentifier(userIdentifier: string, profile: GoogleProfile): Promise<UserModel>;
|
|
11
|
+
}
|
|
12
|
+
export declare function GoogleAuthProvider<UserModel extends AnyUser>(options: GoogleAuthProviderOptions<UserModel>): AuthProvider<UserModel>;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { type } from 'arktype';
|
|
2
|
+
import {} from 'express';
|
|
3
|
+
import { OAuth2Client } from 'google-auth-library';
|
|
4
|
+
import { BadRequest, Unauthorized } from '../errors.js';
|
|
5
|
+
import { getLogger } from '../logger.js';
|
|
6
|
+
import { createTokenResponse } from './token.js';
|
|
7
|
+
import {} from './types.js';
|
|
8
|
+
const logger = getLogger('auth');
|
|
9
|
+
export function GoogleAuthProvider(options) {
|
|
10
|
+
const client = new OAuth2Client({
|
|
11
|
+
clientId: options.clientId,
|
|
12
|
+
clientSecret: options.clientSecret,
|
|
13
|
+
redirectUri: options.redirectUri,
|
|
14
|
+
});
|
|
15
|
+
const verifyAuthorizationCode = async (inputCode) => {
|
|
16
|
+
try {
|
|
17
|
+
const { tokens } = await client.getToken(inputCode);
|
|
18
|
+
return { idToken: tokens.id_token };
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
logger.error(e);
|
|
22
|
+
throw Unauthorized('Invalid authorization code');
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
const verifyIdToken = async (token) => {
|
|
26
|
+
const ticket = await client.verifyIdToken({
|
|
27
|
+
idToken: token,
|
|
28
|
+
audience: options.clientId,
|
|
29
|
+
});
|
|
30
|
+
const payload = ticket.getPayload();
|
|
31
|
+
if (!payload) {
|
|
32
|
+
throw Unauthorized('Token from Google contains invalid payload');
|
|
33
|
+
}
|
|
34
|
+
return payload;
|
|
35
|
+
};
|
|
36
|
+
const requiredScopes = ['openid', 'email', 'profile'];
|
|
37
|
+
return {
|
|
38
|
+
setupRoutes(router, context) {
|
|
39
|
+
const googleUser = { id: 'google' };
|
|
40
|
+
router.get('/google/login', {}, async (req, res) => {
|
|
41
|
+
const state = await context.createTemporaryToken(googleUser);
|
|
42
|
+
const params = new URLSearchParams();
|
|
43
|
+
params.set('client_id', options.clientId);
|
|
44
|
+
params.set('redirect_uri', options.redirectUri);
|
|
45
|
+
params.set('response_type', 'code');
|
|
46
|
+
params.set('scope', requiredScopes.join(' '));
|
|
47
|
+
params.set('access_type', 'offline');
|
|
48
|
+
params.set('prompt', 'select_account');
|
|
49
|
+
params.set('state', state);
|
|
50
|
+
res.redirect(`https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`);
|
|
51
|
+
});
|
|
52
|
+
router.get('/google/callback', {
|
|
53
|
+
query: type({
|
|
54
|
+
code: 'string?',
|
|
55
|
+
error: 'string?',
|
|
56
|
+
scope: 'string?',
|
|
57
|
+
state: 'string?',
|
|
58
|
+
}),
|
|
59
|
+
}, async (req, res) => {
|
|
60
|
+
const { code, error, scope, state } = req.query;
|
|
61
|
+
if (error) {
|
|
62
|
+
logger.error(new Error(`Google callback error: ${error}`));
|
|
63
|
+
return res.redirect('/');
|
|
64
|
+
}
|
|
65
|
+
if (!code || !scope || !state) {
|
|
66
|
+
throw BadRequest('Missing code, scope or state');
|
|
67
|
+
}
|
|
68
|
+
if (!requiredScopes.every((s) => scope.includes(s))) {
|
|
69
|
+
throw BadRequest(`Missing required scopes: ${requiredScopes.join(', ')}`);
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
await context.verifyTemporaryToken(state, googleUser, true);
|
|
73
|
+
}
|
|
74
|
+
catch (e) {
|
|
75
|
+
logger.error(e);
|
|
76
|
+
throw Unauthorized('Invalid state');
|
|
77
|
+
}
|
|
78
|
+
const { idToken } = await verifyAuthorizationCode(code);
|
|
79
|
+
if (!idToken) {
|
|
80
|
+
throw Unauthorized('Invalid id token');
|
|
81
|
+
}
|
|
82
|
+
const payload = await verifyIdToken(idToken);
|
|
83
|
+
logger.info('google payload', { payload });
|
|
84
|
+
const user = await options.getOrCreateUserFromUserIdentifier(payload?.sub, { email: payload.email, fullName: payload.name });
|
|
85
|
+
const accessToken = await context.login(req, res, user);
|
|
86
|
+
logger.info('google-login', { email: user.email });
|
|
87
|
+
if (req.accepts('html')) {
|
|
88
|
+
return res.redirect('/');
|
|
89
|
+
}
|
|
90
|
+
return createTokenResponse(accessToken);
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import {} from 'express';
|
|
1
2
|
import { createTokenResponse } from './token.js';
|
|
2
3
|
import {} from './types.js';
|
|
3
4
|
import { getLogger } from '../logger.js';
|
|
4
|
-
import { route } from '../middleware.js';
|
|
5
5
|
import { DoesNotExistError } from '../postgres.js';
|
|
6
|
-
import {
|
|
6
|
+
import { jsonSchema } from '../validate.js';
|
|
7
7
|
const logger = getLogger('auth');
|
|
8
8
|
export function PasswordlessAuthProvider(options) {
|
|
9
9
|
const getUserOrNull = async (email) => {
|
|
@@ -19,15 +19,17 @@ export function PasswordlessAuthProvider(options) {
|
|
|
19
19
|
};
|
|
20
20
|
return {
|
|
21
21
|
setupRoutes(router, context) {
|
|
22
|
-
router.post('/passwordless/login',
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
22
|
+
router.post('/passwordless/login', {
|
|
23
|
+
request: jsonSchema({
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {
|
|
26
|
+
type: { type: 'string', enum: ['otp', 'token'] },
|
|
27
|
+
otp: { type: 'string' },
|
|
28
|
+
token: { type: 'string' },
|
|
29
|
+
},
|
|
30
|
+
required: ['type'],
|
|
31
|
+
}),
|
|
32
|
+
}, async (req, res) => {
|
|
31
33
|
const { token: temporaryToken, otp, type: tokenType } = req.body;
|
|
32
34
|
const verify = tokenType === 'otp'
|
|
33
35
|
? context.verifyOtp
|
|
@@ -42,17 +44,19 @@ export function PasswordlessAuthProvider(options) {
|
|
|
42
44
|
return res.redirect('/');
|
|
43
45
|
}
|
|
44
46
|
return createTokenResponse(accessToken);
|
|
45
|
-
})
|
|
47
|
+
});
|
|
46
48
|
/**
|
|
47
49
|
* Request a one-time password for login.
|
|
48
50
|
*/
|
|
49
|
-
router.post('/passwordless/request-token',
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
router.post('/passwordless/request-token', {
|
|
52
|
+
request: jsonSchema({
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
email: { type: 'string' },
|
|
56
|
+
},
|
|
57
|
+
required: ['email'],
|
|
58
|
+
}),
|
|
59
|
+
}, async (req) => {
|
|
56
60
|
const { email } = req.body;
|
|
57
61
|
const user = await getUserOrNull(email);
|
|
58
62
|
if (!user) {
|
|
@@ -64,7 +68,7 @@ export function PasswordlessAuthProvider(options) {
|
|
|
64
68
|
await options.onLoginRequest(user, token, otp);
|
|
65
69
|
logger.info('passwordless login requested', { email });
|
|
66
70
|
return 202;
|
|
67
|
-
})
|
|
71
|
+
});
|
|
68
72
|
},
|
|
69
73
|
};
|
|
70
74
|
}
|
package/dist/auth/types.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type RequestHandler, type
|
|
1
|
+
import { type RequestHandler, type Request, type Response } from 'express';
|
|
2
2
|
import { type AccessToken } from './token.js';
|
|
3
|
+
import { type TypedRouter } from '../router.js';
|
|
3
4
|
export interface AnyUser {
|
|
4
5
|
id: string | number;
|
|
5
6
|
[key: string]: any;
|
|
@@ -45,7 +46,7 @@ export interface AuthProviderContext<UserModel extends AnyUser> {
|
|
|
45
46
|
jitter(duration?: number): Promise<void>;
|
|
46
47
|
}
|
|
47
48
|
export interface AuthProvider<UserModel extends AnyUser> {
|
|
48
|
-
setupRoutes(router:
|
|
49
|
+
setupRoutes(router: TypedRouter, context: AuthProviderContext<UserModel>): void;
|
|
49
50
|
}
|
|
50
51
|
export type AuthMiddlewareOptions<UserModel extends AnyUser> = false | {
|
|
51
52
|
isAuthorized?: (user: UserModel) => Promise<boolean> | boolean;
|
package/dist/auth/types.js
CHANGED
package/dist/auth.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { type RequestHandler, type Response, type Request, Router } from 'express';
|
|
1
|
+
import { type RequestHandler, type Response, type Request, type Router } from 'express';
|
|
2
2
|
import { TokenStorage, type AnyUser, type AuthMiddlewareOptions, type AuthOptions, type DecodedTemporaryToken } from './auth/types.js';
|
|
3
3
|
import { type RedisClient } from './redis.js';
|
|
4
4
|
export * from './auth/types.js';
|
|
5
5
|
export * from './auth/emailAndPassword.js';
|
|
6
6
|
export * from './auth/passwordless.js';
|
|
7
7
|
export * from './auth/token.js';
|
|
8
|
+
export * from './auth/google.js';
|
|
8
9
|
export interface CreateAuth<UserModel extends AnyUser> {
|
|
9
10
|
createRouter: () => Router;
|
|
10
11
|
useCurrentUser: () => UserModel | undefined;
|
package/dist/auth.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as crypto from 'node:crypto';
|
|
2
|
-
import {
|
|
2
|
+
import {} from 'express';
|
|
3
3
|
import { InvalidToken, PermissionDenied } from './auth/errors.js';
|
|
4
4
|
import { createAccessToken, decodeAccessToken, extractAccessTokenFromRequest, setAccessTokenCookie, tokenSchema, } from './auth/token.js';
|
|
5
5
|
import { TokenStorage, } from './auth/types.js';
|
|
@@ -7,14 +7,15 @@ import { config } from './config.js';
|
|
|
7
7
|
import { setContext, useContext } from './context.js';
|
|
8
8
|
import { Forbidden, NotFound, Unauthorized } from './errors.js';
|
|
9
9
|
import { getLogger } from './logger.js';
|
|
10
|
-
import { route } from './middleware.js';
|
|
11
10
|
import {} from './redis.js';
|
|
12
|
-
import {
|
|
11
|
+
import { createRouter } from './router.js';
|
|
12
|
+
import { jsonSchema } from './validate.js';
|
|
13
13
|
const logger = getLogger('auth');
|
|
14
14
|
export * from './auth/types.js';
|
|
15
15
|
export * from './auth/emailAndPassword.js';
|
|
16
16
|
export * from './auth/passwordless.js';
|
|
17
17
|
export * from './auth/token.js';
|
|
18
|
+
export * from './auth/google.js';
|
|
18
19
|
export class RedisTokenStorage extends TokenStorage {
|
|
19
20
|
redis;
|
|
20
21
|
keyPrefix;
|
|
@@ -80,24 +81,26 @@ export function createAuth({ getUserById, filterUser, tokenStorage, providers, l
|
|
|
80
81
|
getUserById,
|
|
81
82
|
jitter,
|
|
82
83
|
};
|
|
83
|
-
const
|
|
84
|
-
const router =
|
|
85
|
-
router.get('/me', middleware(),
|
|
84
|
+
const createAuthRouter = () => {
|
|
85
|
+
const router = createRouter();
|
|
86
|
+
router.get('/me', {}, middleware(), () => {
|
|
86
87
|
const user = useCurrentUser();
|
|
87
88
|
return filterUser(user);
|
|
88
|
-
})
|
|
89
|
-
router.get('/logout',
|
|
89
|
+
});
|
|
90
|
+
router.get('/logout', {}, async (req, res) => {
|
|
90
91
|
await logout(req, res);
|
|
91
92
|
res.redirect(loginUrl);
|
|
92
|
-
})
|
|
93
|
+
});
|
|
93
94
|
/**
|
|
94
95
|
* Check that a reset token is valid.
|
|
95
96
|
*/
|
|
96
|
-
router.get('/reset/:token',
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
97
|
+
router.get('/reset/:token', {
|
|
98
|
+
params: jsonSchema({
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: { token: tokenSchema },
|
|
101
|
+
required: ['token'],
|
|
102
|
+
}),
|
|
103
|
+
}, async (req) => {
|
|
101
104
|
const { token } = req.params;
|
|
102
105
|
const value = await tokenStorage.get(token);
|
|
103
106
|
if (!value) {
|
|
@@ -105,8 +108,8 @@ export function createAuth({ getUserById, filterUser, tokenStorage, providers, l
|
|
|
105
108
|
throw NotFound();
|
|
106
109
|
}
|
|
107
110
|
return 200;
|
|
108
|
-
})
|
|
109
|
-
router.get('/token/refresh', middleware(false),
|
|
111
|
+
});
|
|
112
|
+
router.get('/token/refresh', {}, middleware(false), async (req, res) => {
|
|
110
113
|
const user = useCurrentUser();
|
|
111
114
|
if (!user) {
|
|
112
115
|
setAccessTokenCookie(res, null);
|
|
@@ -115,7 +118,7 @@ export function createAuth({ getUserById, filterUser, tokenStorage, providers, l
|
|
|
115
118
|
const { token } = await providerContext.login(req, res, user, true);
|
|
116
119
|
logger.info('refreshed token');
|
|
117
120
|
return { token, user: filterUser(user) };
|
|
118
|
-
})
|
|
121
|
+
});
|
|
119
122
|
for (const provider of providers) {
|
|
120
123
|
provider.setupRoutes(router, providerContext);
|
|
121
124
|
}
|
|
@@ -213,18 +216,19 @@ export function createAuth({ getUserById, filterUser, tokenStorage, providers, l
|
|
|
213
216
|
*/
|
|
214
217
|
async function createTemporaryToken(user) {
|
|
215
218
|
try {
|
|
219
|
+
const uid = user === null ? 'anonymous' : user.id;
|
|
216
220
|
const randomBytes = crypto.randomBytes(32).toString('hex');
|
|
217
|
-
const payload = { v: '1', uid:
|
|
221
|
+
const payload = { v: '1', uid: uid, r: randomBytes };
|
|
218
222
|
const signature = crypto
|
|
219
223
|
.createHmac('sha256', config.value.AUTH_SECRET)
|
|
220
224
|
.update(JSON.stringify(payload))
|
|
221
225
|
.digest('base64')
|
|
222
226
|
.slice(0, 12);
|
|
223
|
-
|
|
224
|
-
if (!user.id) {
|
|
227
|
+
if (!uid) {
|
|
225
228
|
throw new Error('User ID is required to create a token');
|
|
226
229
|
}
|
|
227
|
-
|
|
230
|
+
const temporaryToken = Buffer.from(JSON.stringify({ payload, signature })).toString('base64url');
|
|
231
|
+
await tokenStorage.set(temporaryToken, String(uid), config.value.AUTH_TEMP_TOKEN_EXPIRES_IN);
|
|
228
232
|
return temporaryToken;
|
|
229
233
|
}
|
|
230
234
|
catch (error) {
|
|
@@ -245,14 +249,14 @@ export function createAuth({ getUserById, filterUser, tokenStorage, providers, l
|
|
|
245
249
|
if (expectedSignature !== decoded.signature) {
|
|
246
250
|
throw new InvalidToken('Invalid signature');
|
|
247
251
|
}
|
|
248
|
-
if (user && decoded.payload.uid !== user.id) {
|
|
252
|
+
if (user !== null && decoded.payload.uid !== user.id) {
|
|
249
253
|
throw new InvalidToken(`Invalid user ${user.id}`);
|
|
250
254
|
}
|
|
251
255
|
const storedUid = await tokenStorage.get(token);
|
|
252
256
|
if (!storedUid) {
|
|
253
257
|
throw new InvalidToken('Invalid token not found');
|
|
254
258
|
}
|
|
255
|
-
if (user && storedUid !== user.id) {
|
|
259
|
+
if (user !== null && storedUid !== user.id) {
|
|
256
260
|
throw new InvalidToken(`Invalid user ${user.id}`);
|
|
257
261
|
}
|
|
258
262
|
if (consume) {
|
|
@@ -263,7 +267,7 @@ export function createAuth({ getUserById, filterUser, tokenStorage, providers, l
|
|
|
263
267
|
return {
|
|
264
268
|
before: () => middleware(false),
|
|
265
269
|
isAuthorized: (fn) => middleware({ isAuthorized: fn || (() => true) }),
|
|
266
|
-
createRouter,
|
|
270
|
+
createRouter: () => createAuthRouter().getRouter(),
|
|
267
271
|
useCurrentUser,
|
|
268
272
|
middleware,
|
|
269
273
|
createTemporaryToken,
|
package/dist/cookie-consent.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
1
|
import { getLogger } from './logger.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { createRouter } from './router.js';
|
|
3
|
+
import { jsonSchema } from './validate.js';
|
|
5
4
|
const logger = getLogger();
|
|
6
5
|
export function cookieConsent({ onConsent, }) {
|
|
7
|
-
const router =
|
|
8
|
-
router.get('/',
|
|
6
|
+
const router = createRouter();
|
|
7
|
+
router.get('/', {}, async () => {
|
|
9
8
|
return {
|
|
10
9
|
consents: [],
|
|
11
10
|
};
|
|
12
|
-
})
|
|
13
|
-
router.post('/',
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
});
|
|
12
|
+
router.post('/', {
|
|
13
|
+
request: jsonSchema({
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
consents: {
|
|
17
|
+
type: 'array',
|
|
18
|
+
items: { type: 'string' },
|
|
19
|
+
},
|
|
19
20
|
},
|
|
20
|
-
},
|
|
21
|
-
}
|
|
21
|
+
}),
|
|
22
|
+
}, async (req, res) => {
|
|
22
23
|
const { consents } = req.body;
|
|
23
24
|
res.cookie('cookie_consent', consents.join(',') || '1', {
|
|
24
25
|
expires: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000),
|
|
@@ -28,6 +29,6 @@ export function cookieConsent({ onConsent, }) {
|
|
|
28
29
|
await onConsent?.(consents);
|
|
29
30
|
logger.info('cookie consent');
|
|
30
31
|
return { consents };
|
|
31
|
-
})
|
|
32
|
-
return router;
|
|
32
|
+
});
|
|
33
|
+
return router.getRouter();
|
|
33
34
|
}
|
package/dist/manage.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
|
+
import { createClient } from '@hey-api/openapi-ts';
|
|
1
3
|
import { program } from 'commander';
|
|
2
4
|
import {} from 'express';
|
|
3
5
|
import { hashPassword } from './auth.js';
|
|
@@ -59,4 +61,34 @@ export function registerDefaultCommands(app) {
|
|
|
59
61
|
}
|
|
60
62
|
}));
|
|
61
63
|
});
|
|
64
|
+
registerManagementCommand((command, cli) => {
|
|
65
|
+
command('generate-api')
|
|
66
|
+
.option('--output <string>', 'Output directory', 'src/types/api-gen')
|
|
67
|
+
.action(cli.wrap(async (options) => {
|
|
68
|
+
const openApiFile = resolve('./openapi.json');
|
|
69
|
+
await createClient({
|
|
70
|
+
input: {
|
|
71
|
+
path: openApiFile,
|
|
72
|
+
},
|
|
73
|
+
output: {
|
|
74
|
+
format: 'prettier',
|
|
75
|
+
path: options.output,
|
|
76
|
+
lint: 'eslint',
|
|
77
|
+
tsConfigPath: resolve('./tsconfig.json'),
|
|
78
|
+
},
|
|
79
|
+
plugins: [
|
|
80
|
+
{
|
|
81
|
+
enums: 'javascript',
|
|
82
|
+
name: '@hey-api/typescript',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
asClass: true,
|
|
86
|
+
classStructure: 'off',
|
|
87
|
+
classNameBuilder: (name) => `${name}Service`,
|
|
88
|
+
name: '@hey-api/sdk',
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
}));
|
|
93
|
+
});
|
|
62
94
|
}
|
package/dist/router.d.ts
CHANGED
|
@@ -12,25 +12,25 @@ export type ExtractParams<Path extends string> = Prettify<Path extends `${string
|
|
|
12
12
|
} & ExtractParams<`/${Rest}`> : Path extends `${string}:${infer Param}` ? {
|
|
13
13
|
[K in Param extends `${infer P}?` ? P : Param]: string;
|
|
14
14
|
} : {}>;
|
|
15
|
-
export type TypedRequestHandler<Params, ResponseBodySchema extends StandardSchemaV1 | undefined, RequestBodySchema extends StandardSchemaV1 | undefined, RequestQuery extends StandardSchemaV1 | undefined> = (req: Request<Params, never, RequestBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<RequestBodySchema> : unknown, RequestQuery extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<RequestQuery> : Record<string, string | string[]>>, res: Response<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown>, next: NextFunction) => ResponseWrapper<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown> | Promise<ResponseWrapper<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown>>;
|
|
15
|
+
export type TypedRequestHandler<Params, ResponseBodySchema extends StandardSchemaV1 | undefined, RequestBodySchema extends StandardSchemaV1 | undefined, RequestQuery extends StandardSchemaV1 | undefined, RequestParamsSchema extends StandardSchemaV1 | undefined = undefined> = (req: Request<Params, never, RequestBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<RequestBodySchema> : unknown, RequestQuery extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<RequestQuery> : Record<string, string | string[]>>, res: Response<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown>, next: NextFunction) => ResponseWrapper<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown> | Promise<ResponseWrapper<ResponseBodySchema extends StandardSchemaV1<any> ? StandardSchemaV1.InferOutput<ResponseBodySchema> : unknown>>;
|
|
16
16
|
export declare class TypedRouter {
|
|
17
17
|
#private;
|
|
18
18
|
router: import("express-serve-static-core").Router;
|
|
19
|
-
get<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
|
|
19
|
+
get<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined, RequestParamsSchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema, RequestParamsSchema>, ...middleware: [
|
|
20
20
|
...RequestHandler[],
|
|
21
|
-
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
|
|
21
|
+
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema, RequestParamsSchema>
|
|
22
22
|
]): this;
|
|
23
|
-
post<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
|
|
23
|
+
post<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined, RequestParamsSchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema, RequestParamsSchema>, ...middleware: [
|
|
24
24
|
...RequestHandler[],
|
|
25
|
-
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
|
|
25
|
+
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema, RequestParamsSchema>
|
|
26
26
|
]): this;
|
|
27
|
-
put<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
|
|
27
|
+
put<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined, RequestParamsSchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema, RequestParamsSchema>, ...middleware: [
|
|
28
28
|
...RequestHandler[],
|
|
29
29
|
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
|
|
30
30
|
]): this;
|
|
31
|
-
delete<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
|
|
31
|
+
delete<Path extends string, RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined, RequestParamsSchema extends StandardSchemaV1<any> | undefined = undefined>(path: Path, validation: ValidationSpec<RequestBodySchema, ResponseBodySchema, RequestQuerySchema>, ...middleware: [
|
|
32
32
|
...RequestHandler[],
|
|
33
|
-
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema>
|
|
33
|
+
TypedRequestHandler<ExtractParams<Path>, ResponseBodySchema, RequestBodySchema, RequestQuerySchema, RequestParamsSchema>
|
|
34
34
|
]): this;
|
|
35
35
|
use(...middleware: RequestHandler[]): this;
|
|
36
36
|
use(...middleware: [string, ...RequestHandler[]]): this;
|
package/dist/routes.d.ts
CHANGED
package/dist/routes.js
CHANGED
|
@@ -143,14 +143,54 @@ const mapKeysToPath = (layerRegexPath, keys) => {
|
|
|
143
143
|
.replace(/\\/gi, '')
|
|
144
144
|
.replace(/\/{2,}/gi, '/');
|
|
145
145
|
};
|
|
146
|
+
function extractTagsFromPath(path) {
|
|
147
|
+
const segments = path
|
|
148
|
+
.replace(/^\/api\/v\d+\//, '') // Remove /api/v1, /api/v2, etc.
|
|
149
|
+
.split('/')
|
|
150
|
+
.filter(Boolean);
|
|
151
|
+
if (segments.length === 0) {
|
|
152
|
+
return ['Default'];
|
|
153
|
+
}
|
|
154
|
+
// Use the first segment as the primary tag
|
|
155
|
+
const primaryTag = segments[0].charAt(0).toUpperCase() + segments[0].slice(1);
|
|
156
|
+
return [primaryTag];
|
|
157
|
+
}
|
|
158
|
+
function convertPathToOperationId(method, path) {
|
|
159
|
+
// Clean the path and split into segments
|
|
160
|
+
const segments = path
|
|
161
|
+
.replace(/[{}]/g, '') // Remove OpenAPI parameter braces
|
|
162
|
+
.replace(/[^a-zA-Z0-9/-]/g, '') // Keep hyphens, remove other non-alphanumeric chars
|
|
163
|
+
.replace(/^\/api\/v\d+\//, '') // Remove /api/v1, /api/v2, etc. prefixes
|
|
164
|
+
.split('/') // Split by slashes
|
|
165
|
+
.filter(Boolean) // Remove empty strings
|
|
166
|
+
.map((segment) => {
|
|
167
|
+
// Handle kebab-case segments by splitting on hyphens and camelizing
|
|
168
|
+
return segment
|
|
169
|
+
.split('-')
|
|
170
|
+
.map((word, index) => index === 0
|
|
171
|
+
? word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
|
|
172
|
+
: word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
|
|
173
|
+
.join('');
|
|
174
|
+
}); // Capitalize each segment
|
|
175
|
+
const camelizedPath = segments.join('');
|
|
176
|
+
return `${method.toLowerCase()}${camelizedPath}`;
|
|
177
|
+
}
|
|
146
178
|
export function toOpenAPISpec(app, info = {
|
|
147
179
|
title: 'API Documentation',
|
|
148
180
|
version: '1.0.0',
|
|
149
181
|
}) {
|
|
150
182
|
const routes = parseRoutes(app);
|
|
151
183
|
const paths = {};
|
|
184
|
+
const allTags = new Set();
|
|
152
185
|
for (const routeInfo of routes) {
|
|
153
186
|
const { method, path, params, query, body, response } = routeInfo;
|
|
187
|
+
if (!path.startsWith('/api/')) {
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
const tags = extractTagsFromPath(path);
|
|
191
|
+
for (const tag of tags) {
|
|
192
|
+
allTags.add(tag);
|
|
193
|
+
}
|
|
154
194
|
// Convert Express path params to OpenAPI path params
|
|
155
195
|
const openApiPath = path.replace(/:([^/]+)/g, '{$1}');
|
|
156
196
|
if (!paths[openApiPath]) {
|
|
@@ -178,6 +218,8 @@ export function toOpenAPISpec(app, info = {
|
|
|
178
218
|
})));
|
|
179
219
|
}
|
|
180
220
|
paths[openApiPath][method.toLowerCase()] = {
|
|
221
|
+
operationId: convertPathToOperationId(method, path), // Use original path, not openApiPath
|
|
222
|
+
tags,
|
|
181
223
|
parameters,
|
|
182
224
|
...(body && {
|
|
183
225
|
requestBody: {
|
|
@@ -232,6 +274,7 @@ export function toOpenAPISpec(app, info = {
|
|
|
232
274
|
version: info.version,
|
|
233
275
|
...(info.description && { description: info.description }),
|
|
234
276
|
},
|
|
277
|
+
tags: Array.from(allTags).map((tag) => ({ name: tag })),
|
|
235
278
|
paths,
|
|
236
279
|
};
|
|
237
280
|
}
|
package/dist/validate.d.ts
CHANGED
|
@@ -39,10 +39,11 @@ export type ValidationResult = {
|
|
|
39
39
|
isValid: false;
|
|
40
40
|
errors: ValidationErrors;
|
|
41
41
|
};
|
|
42
|
-
export interface ValidationSpec<RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined> {
|
|
42
|
+
export interface ValidationSpec<RequestBodySchema extends StandardSchemaV1<any> | undefined = undefined, ResponseBodySchema extends StandardSchemaV1<any> | undefined = undefined, RequestQuerySchema extends StandardSchemaV1<any> | undefined = undefined, RequestParamsSchema extends StandardSchemaV1<any> | undefined = undefined> {
|
|
43
43
|
request?: RequestBodySchema;
|
|
44
44
|
response?: ResponseBodySchema;
|
|
45
45
|
query?: RequestQuerySchema;
|
|
46
|
+
params?: RequestParamsSchema;
|
|
46
47
|
}
|
|
47
48
|
export declare function validateSchema(data: unknown, schema: StandardSchemaV1<any>): Promise<ValidationResult>;
|
|
48
49
|
export declare function validateStandardSchema(schemas: {
|
|
@@ -5,6 +5,13 @@ import Sonda from 'sonda/vite';
|
|
|
5
5
|
import { defineConfig } from 'vite';
|
|
6
6
|
import { readEnv } from '../utils.js';
|
|
7
7
|
export function defineViteConfig({ base = '', reactCompiler = false, ssr = false, analyze = readEnv.boolean('VITE_ANALYZE', false), }) {
|
|
8
|
+
const allowedHosts = [
|
|
9
|
+
'localhost',
|
|
10
|
+
'127.0.0.1',
|
|
11
|
+
// Support puma-dev by default
|
|
12
|
+
`${process.cwd().split('/').pop()}.local`,
|
|
13
|
+
`${process.cwd().split('/').pop()}.test`,
|
|
14
|
+
];
|
|
8
15
|
const port = readEnv.number('VITE_PORT', 5173);
|
|
9
16
|
return defineConfig({
|
|
10
17
|
plugins: [
|
|
@@ -20,7 +27,7 @@ export function defineViteConfig({ base = '', reactCompiler = false, ssr = false
|
|
|
20
27
|
Sonda({
|
|
21
28
|
enabled: analyze,
|
|
22
29
|
open: false,
|
|
23
|
-
|
|
30
|
+
deep: true,
|
|
24
31
|
sources: true,
|
|
25
32
|
}),
|
|
26
33
|
],
|
|
@@ -53,8 +60,9 @@ export function defineViteConfig({ base = '', reactCompiler = false, ssr = false
|
|
|
53
60
|
'/api': `http://localhost:${readEnv.number('PORT', 4000)}`,
|
|
54
61
|
'/analytics.js': `http://localhost:${readEnv.number('PORT', 4000)}`,
|
|
55
62
|
},
|
|
63
|
+
allowedHosts,
|
|
56
64
|
}
|
|
57
|
-
: {},
|
|
65
|
+
: { allowedHosts },
|
|
58
66
|
ssr: ssr
|
|
59
67
|
? {
|
|
60
68
|
noExternal: ['react-helmet-async'],
|
|
@@ -65,12 +73,5 @@ export function defineViteConfig({ base = '', reactCompiler = false, ssr = false
|
|
|
65
73
|
include: ['src/**/*.{test,spec}.?(c|m)[jt]s?(x)'],
|
|
66
74
|
globals: true,
|
|
67
75
|
},
|
|
68
|
-
css: {
|
|
69
|
-
preprocessorOptions: {
|
|
70
|
-
scss: {
|
|
71
|
-
api: 'modern-compiler',
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
76
|
});
|
|
76
77
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devmoods/express-extras",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.0",
|
|
4
4
|
"description": "Kubernetes-ready utilities for rapid Node.js application development",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -44,55 +44,57 @@
|
|
|
44
44
|
],
|
|
45
45
|
"license": "ISC",
|
|
46
46
|
"devDependencies": {
|
|
47
|
-
"@devmoods/eslint-config": "^2.
|
|
47
|
+
"@devmoods/eslint-config": "^2.12.0",
|
|
48
48
|
"@types/koa-compose": "^3.2.8",
|
|
49
|
-
"@types/node": "
|
|
49
|
+
"@types/node": "24.3.0",
|
|
50
50
|
"@types/supertest": "^6.0.3",
|
|
51
|
-
"@vitejs/plugin-react": "^
|
|
52
|
-
"@vitest/coverage-v8": "^3.
|
|
51
|
+
"@vitejs/plugin-react": "^5.0.2",
|
|
52
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
53
53
|
"arktype": "^2.1.20",
|
|
54
54
|
"cookiejar": "^2.1.4",
|
|
55
|
-
"eslint": "^9.
|
|
56
|
-
"faktory-worker": "^4.7.
|
|
57
|
-
"prettier": "3.
|
|
58
|
-
"supertest": "7.1.
|
|
55
|
+
"eslint": "^9.34.0",
|
|
56
|
+
"faktory-worker": "^4.7.1",
|
|
57
|
+
"prettier": "3.6.2",
|
|
58
|
+
"supertest": "7.1.4",
|
|
59
59
|
"ts-node": "10.9.2",
|
|
60
|
-
"tsx": "^4.
|
|
61
|
-
"typescript": "5.
|
|
62
|
-
"vite": "^
|
|
63
|
-
"vitest": "^3.
|
|
64
|
-
"vitest-fail-on-console": "^0.
|
|
60
|
+
"tsx": "^4.20.5",
|
|
61
|
+
"typescript": "5.9.2",
|
|
62
|
+
"vite": "^7.1.3",
|
|
63
|
+
"vitest": "^3.2.4",
|
|
64
|
+
"vitest-fail-on-console": "^0.9.0"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@devmoods/fetch": "^4.1.0",
|
|
68
|
-
"@
|
|
69
|
-
"@sentry/
|
|
68
|
+
"@hey-api/openapi-ts": "^0.82.1",
|
|
69
|
+
"@sentry/node": "^10.8.0",
|
|
70
|
+
"@sentry/profiling-node": "^10.8.0",
|
|
70
71
|
"@standard-schema/spec": "^1.0.0",
|
|
71
|
-
"@types/bcrypt": "^
|
|
72
|
-
"@types/cookie-parser": "^1.4.
|
|
73
|
-
"@types/express": "^5.0.
|
|
74
|
-
"@types/jsonwebtoken": "^9.0.
|
|
75
|
-
"@types/nodemailer": "^
|
|
76
|
-
"@types/pg": "^8.15.
|
|
72
|
+
"@types/bcrypt": "^6.0.0",
|
|
73
|
+
"@types/cookie-parser": "^1.4.9",
|
|
74
|
+
"@types/express": "^5.0.3",
|
|
75
|
+
"@types/jsonwebtoken": "^9.0.10",
|
|
76
|
+
"@types/nodemailer": "^7.0.1",
|
|
77
|
+
"@types/pg": "^8.15.5",
|
|
77
78
|
"@types/qs": "^6.14.0",
|
|
78
79
|
"@types/superagent": "^8.1.9",
|
|
79
80
|
"@types/uuid": "^10.0.0",
|
|
80
81
|
"ajv": "^8.17.1",
|
|
81
|
-
"argon2": "^0.
|
|
82
|
+
"argon2": "^0.44.0",
|
|
82
83
|
"bcrypt": "^6.0.0",
|
|
83
|
-
"chalk": "^5.
|
|
84
|
+
"chalk": "^5.6.0",
|
|
84
85
|
"commander": "^14.0.0",
|
|
85
86
|
"compose-middleware": "^5.0.1",
|
|
86
87
|
"cookie-parser": "^1.4.7",
|
|
87
|
-
"dotenv": "^
|
|
88
|
+
"dotenv": "^17.2.1",
|
|
88
89
|
"express": "^4.21.2",
|
|
90
|
+
"google-auth-library": "^10.3.0",
|
|
89
91
|
"helmet": "^8.1.0",
|
|
90
92
|
"jsonwebtoken": "^9.0.2",
|
|
91
|
-
"nodemailer": "^7.0.
|
|
92
|
-
"pg": "^8.16.
|
|
93
|
-
"redis": "^5.
|
|
94
|
-
"sonda": "^0.
|
|
95
|
-
"sql-formatter": "^15.6.
|
|
93
|
+
"nodemailer": "^7.0.6",
|
|
94
|
+
"pg": "^8.16.3",
|
|
95
|
+
"redis": "^5.8.2",
|
|
96
|
+
"sonda": "^0.9.0",
|
|
97
|
+
"sql-formatter": "^15.6.6",
|
|
96
98
|
"uuid": "^11.1.0"
|
|
97
99
|
},
|
|
98
100
|
"prettier": {
|
|
@@ -106,7 +108,7 @@
|
|
|
106
108
|
"vite": ">=6.0.5"
|
|
107
109
|
},
|
|
108
110
|
"engines": {
|
|
109
|
-
"node": ">=20 <
|
|
111
|
+
"node": ">=20 <25"
|
|
110
112
|
},
|
|
111
113
|
"scripts": {
|
|
112
114
|
"start": "pushd example; pnpm run start --watch; popd example;",
|