sentri 1.0.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 +1044 -0
- package/dist/client.d.ts +158 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +49 -0
- package/dist/client.js.map +1 -0
- package/dist/errors/AuthError.d.ts +40 -0
- package/dist/errors/AuthError.d.ts.map +1 -0
- package/dist/errors/AuthError.js +29 -0
- package/dist/errors/AuthError.js.map +1 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/libs/config.d.ts +18 -0
- package/dist/libs/config.d.ts.map +1 -0
- package/dist/libs/config.js +59 -0
- package/dist/libs/config.js.map +1 -0
- package/dist/libs/hash.d.ts +3 -0
- package/dist/libs/hash.d.ts.map +1 -0
- package/dist/libs/hash.js +8 -0
- package/dist/libs/hash.js.map +1 -0
- package/dist/libs/token.d.ts +8 -0
- package/dist/libs/token.d.ts.map +1 -0
- package/dist/libs/token.js +54 -0
- package/dist/libs/token.js.map +1 -0
- package/dist/middleware/authorize.d.ts +3 -0
- package/dist/middleware/authorize.d.ts.map +1 -0
- package/dist/middleware/authorize.js +15 -0
- package/dist/middleware/authorize.js.map +1 -0
- package/dist/middleware/permit.d.ts +62 -0
- package/dist/middleware/permit.d.ts.map +1 -0
- package/dist/middleware/permit.js +61 -0
- package/dist/middleware/permit.js.map +1 -0
- package/dist/middleware/protect.d.ts +4 -0
- package/dist/middleware/protect.d.ts.map +1 -0
- package/dist/middleware/protect.js +19 -0
- package/dist/middleware/protect.js.map +1 -0
- package/dist/middleware/router.d.ts +27 -0
- package/dist/middleware/router.d.ts.map +1 -0
- package/dist/middleware/router.js +244 -0
- package/dist/middleware/router.js.map +1 -0
- package/dist/services/auth.d.ts +7 -0
- package/dist/services/auth.d.ts.map +1 -0
- package/dist/services/auth.js +84 -0
- package/dist/services/auth.js.map +1 -0
- package/dist/types/auth.d.ts +234 -0
- package/dist/types/auth.d.ts.map +1 -0
- package/dist/types/auth.js +2 -0
- package/dist/types/auth.js.map +1 -0
- package/package.json +38 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { PermitCheck, PermitOptions } from './middleware/permit.js';
|
|
2
|
+
import type { AuthConfig, AuthResult, AuthUser, LoginInput, RefreshResult, SignupInput } from './types/auth.js';
|
|
3
|
+
import type { RequestHandler, Router } from 'express';
|
|
4
|
+
/**
|
|
5
|
+
* The bound auth client returned by {@link createAuth}.
|
|
6
|
+
*
|
|
7
|
+
* All methods are pre-configured with the options passed to `createAuth` —
|
|
8
|
+
* you never need to pass config around yourself.
|
|
9
|
+
*
|
|
10
|
+
* `TRole` is inferred from `validRoles` and narrows role strings to your
|
|
11
|
+
* application's exact union type everywhere (signup, authorize, req.user, etc.).
|
|
12
|
+
*/
|
|
13
|
+
export interface AuthClient<TRole extends string = string> {
|
|
14
|
+
/**
|
|
15
|
+
* Register a new user.
|
|
16
|
+
*
|
|
17
|
+
* Validates that every requested role is in `validRoles`, rejects duplicate
|
|
18
|
+
* emails, hashes the password, creates the user record, and returns a fresh
|
|
19
|
+
* access + refresh token pair.
|
|
20
|
+
*/
|
|
21
|
+
signup(input: SignupInput<TRole>): Promise<AuthResult<TRole>>;
|
|
22
|
+
/**
|
|
23
|
+
* Authenticate an existing user by email and password.
|
|
24
|
+
*
|
|
25
|
+
* On success, creates a new session and returns an access + refresh token pair.
|
|
26
|
+
* Returns `{ success: false, error }` with code `INVALID_CREDENTIALS` on any
|
|
27
|
+
* mismatch (intentionally vague to prevent user enumeration).
|
|
28
|
+
*/
|
|
29
|
+
login(input: LoginInput): Promise<AuthResult<TRole>>;
|
|
30
|
+
/**
|
|
31
|
+
* Exchange a valid refresh token for a new access + refresh token pair.
|
|
32
|
+
*
|
|
33
|
+
* Verifies the JWT, looks up the session in the database, checks expiry,
|
|
34
|
+
* then **rotates** the session: the old session is deleted and a new one is
|
|
35
|
+
* created. Old refresh tokens are immediately invalidated.
|
|
36
|
+
*/
|
|
37
|
+
refresh(refreshToken: string): Promise<RefreshResult<TRole>>;
|
|
38
|
+
/**
|
|
39
|
+
* Invalidate a single session identified by the refresh token.
|
|
40
|
+
*
|
|
41
|
+
* Safe to call even if the token is already expired — it will simply
|
|
42
|
+
* attempt a DB delete and resolve.
|
|
43
|
+
*/
|
|
44
|
+
logout(refreshToken: string): Promise<void>;
|
|
45
|
+
/**
|
|
46
|
+
* Delete all sessions for a user, effectively logging them out of every device.
|
|
47
|
+
*
|
|
48
|
+
* @param userId - The user's primary key as stored in the database.
|
|
49
|
+
*/
|
|
50
|
+
logoutAll(userId: string): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Express middleware factory that enforces authentication.
|
|
53
|
+
*
|
|
54
|
+
* Reads the `Authorization: Bearer <token>` header, verifies the access token,
|
|
55
|
+
* and injects the decoded payload as `req.user`. Calls `next(AuthError)` on failure.
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* router.get('/me', auth.protect(), (req, res) => {
|
|
59
|
+
* res.json(req.user);
|
|
60
|
+
* });
|
|
61
|
+
*/
|
|
62
|
+
protect(): RequestHandler;
|
|
63
|
+
/**
|
|
64
|
+
* Express middleware factory that enforces role-based access.
|
|
65
|
+
*
|
|
66
|
+
* Must be used **after** `protect()`. Passes if the authenticated user has
|
|
67
|
+
* at least one of the specified roles; otherwise calls `next(AuthError)` with
|
|
68
|
+
* code `FORBIDDEN`.
|
|
69
|
+
*
|
|
70
|
+
* @example
|
|
71
|
+
* router.delete('/posts/:id', auth.protect(), auth.authorize('admin'), handler);
|
|
72
|
+
*/
|
|
73
|
+
authorize(...roles: TRole[]): RequestHandler;
|
|
74
|
+
/** Hash a plain-text password using the configured `saltRounds`. */
|
|
75
|
+
hashPassword(plain: string): Promise<string>;
|
|
76
|
+
/** Compare a plain-text password against a stored bcrypt hash. */
|
|
77
|
+
verifyPassword(plain: string, hash: string): Promise<boolean>;
|
|
78
|
+
/** Sign an access token for the given user payload. */
|
|
79
|
+
signAccessToken(payload: AuthUser<TRole>): string;
|
|
80
|
+
/** Sign a refresh token bound to a session ID. */
|
|
81
|
+
signRefreshToken(sessionId: string): string;
|
|
82
|
+
/** Verify and decode an access token. Throws `AuthError` if invalid or expired. */
|
|
83
|
+
verifyAccessToken(token: string): AuthUser<TRole>;
|
|
84
|
+
/** Verify and decode a refresh token. Throws `AuthError` if invalid or expired. */
|
|
85
|
+
verifyRefreshToken(token: string): {
|
|
86
|
+
sessionId: string;
|
|
87
|
+
};
|
|
88
|
+
/**
|
|
89
|
+
* Returns a pre-built Express Router with all standard auth endpoints mounted:
|
|
90
|
+
*
|
|
91
|
+
* - `POST /signup` — register, returns `{ accessToken, refreshToken, user }`
|
|
92
|
+
* - `POST /login` — authenticate, returns `{ accessToken, refreshToken, user }`
|
|
93
|
+
* - `POST /refresh` — rotate refresh token, returns `{ accessToken, refreshToken }`
|
|
94
|
+
* - `POST /logout` — invalidate current session
|
|
95
|
+
* - `POST /logout-all` — invalidate all sessions (requires valid access token)
|
|
96
|
+
*
|
|
97
|
+
* Requires `express.json()` to be applied before the router.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* app.use(express.json());
|
|
101
|
+
* app.use('/auth', auth.router());
|
|
102
|
+
*/
|
|
103
|
+
router(): Router;
|
|
104
|
+
/**
|
|
105
|
+
* Express middleware factory for resource-level permission checks.
|
|
106
|
+
*
|
|
107
|
+
* Must be used **after** `protect()`. Evaluates a check function against the
|
|
108
|
+
* current request and calls `next(AuthError)` with `FORBIDDEN` if it returns `false`.
|
|
109
|
+
*
|
|
110
|
+
* Accepts either a bare check function or an options object with an optional
|
|
111
|
+
* `roles` list whose members bypass the check entirely.
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* // User can only update their own profile
|
|
115
|
+
* router.put('/users/:id',
|
|
116
|
+
* auth.protect(),
|
|
117
|
+
* auth.permit((req) => req.user!.id === req.params['id']),
|
|
118
|
+
* handler,
|
|
119
|
+
* );
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* // Admins bypass the check; others must own the resource
|
|
123
|
+
* router.delete('/posts/:id',
|
|
124
|
+
* auth.protect(),
|
|
125
|
+
* auth.permit({
|
|
126
|
+
* roles: ['admin'],
|
|
127
|
+
* check: async (req) => {
|
|
128
|
+
* const post = await db.post.findUnique({ where: { id: req.params['id'] } });
|
|
129
|
+
* return post?.authorId === req.user!.id;
|
|
130
|
+
* },
|
|
131
|
+
* }),
|
|
132
|
+
* handler,
|
|
133
|
+
* );
|
|
134
|
+
*/
|
|
135
|
+
permit(check: PermitCheck): RequestHandler;
|
|
136
|
+
permit(options: PermitOptions<TRole>): RequestHandler;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Create a fully configured auth client for your application.
|
|
140
|
+
*
|
|
141
|
+
* Pass your config once here and use the returned client everywhere — it
|
|
142
|
+
* binds all library functions to your settings so you never need to pass
|
|
143
|
+
* config manually.
|
|
144
|
+
*
|
|
145
|
+
* The generic parameter `TRole` is inferred automatically from `validRoles`
|
|
146
|
+
* when you use `as const`:
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* export const auth = createAuth({
|
|
150
|
+
* secret: process.env.JWT_SECRET!,
|
|
151
|
+
* validRoles: ['user', 'admin', 'moderator'] as const,
|
|
152
|
+
* adapter: myAdapter,
|
|
153
|
+
* });
|
|
154
|
+
*
|
|
155
|
+
* // auth.authorize('admin') is type-safe — 'superuser' would be a compile error.
|
|
156
|
+
*/
|
|
157
|
+
export declare function createAuth<TRole extends string = string>(config: AuthConfig<TRole>): AuthClient<TRole>;
|
|
158
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACzE,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,QAAQ,EACR,UAAU,EACV,aAAa,EACb,WAAW,EACZ,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAEtD;;;;;;;;GAQG;AACH,MAAM,WAAW,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM;IACvD;;;;;;OAMG;IACH,MAAM,CAAC,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAE9D;;;;;;OAMG;IACH,KAAK,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC;IAErD;;;;;;OAMG;IACH,OAAO,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC;IAE7D;;;;;OAKG;IACH,MAAM,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAE5C;;;;OAIG;IACH,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAEzC;;;;;;;;;;OAUG;IACH,OAAO,IAAI,cAAc,CAAC;IAE1B;;;;;;;;;OASG;IACH,SAAS,CAAC,GAAG,KAAK,EAAE,KAAK,EAAE,GAAG,cAAc,CAAC;IAE7C,oEAAoE;IACpE,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAE7C,kEAAkE;IAClE,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAE9D,uDAAuD;IACvD,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC;IAElD,kDAAkD;IAClD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAAC;IAE5C,mFAAmF;IACnF,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAElD,mFAAmF;IACnF,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG;QAAE,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;IAEzD;;;;;;;;;;;;;;OAcG;IACH,MAAM,IAAI,MAAM,CAAC;IAEjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8BG;IACH,MAAM,CAAC,KAAK,EAAE,WAAW,GAAG,cAAc,CAAC;IAC3C,MAAM,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC;CACvD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,UAAU,CAAC,KAAK,SAAS,MAAM,GAAG,MAAM,EACtD,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,GACxB,UAAU,CAAC,KAAK,CAAC,CAqBnB"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { hashPassword, verifyPassword } from './libs/hash.js';
|
|
2
|
+
import { signAccessToken, signRefreshToken, verifyAccessToken, verifyRefreshToken } from './libs/token.js';
|
|
3
|
+
import { resolveConfig, validateConfig } from './libs/config.js';
|
|
4
|
+
import { signup, login, refresh, logout, logoutAll } from './services/auth.js';
|
|
5
|
+
import { protect } from './middleware/protect.js';
|
|
6
|
+
import { authorize } from './middleware/authorize.js';
|
|
7
|
+
import { permit } from './middleware/permit.js';
|
|
8
|
+
import { createAuthRouter } from './middleware/router.js';
|
|
9
|
+
/**
|
|
10
|
+
* Create a fully configured auth client for your application.
|
|
11
|
+
*
|
|
12
|
+
* Pass your config once here and use the returned client everywhere — it
|
|
13
|
+
* binds all library functions to your settings so you never need to pass
|
|
14
|
+
* config manually.
|
|
15
|
+
*
|
|
16
|
+
* The generic parameter `TRole` is inferred automatically from `validRoles`
|
|
17
|
+
* when you use `as const`:
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* export const auth = createAuth({
|
|
21
|
+
* secret: process.env.JWT_SECRET!,
|
|
22
|
+
* validRoles: ['user', 'admin', 'moderator'] as const,
|
|
23
|
+
* adapter: myAdapter,
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* // auth.authorize('admin') is type-safe — 'superuser' would be a compile error.
|
|
27
|
+
*/
|
|
28
|
+
export function createAuth(config) {
|
|
29
|
+
validateConfig(config);
|
|
30
|
+
const resolved = resolveConfig(config);
|
|
31
|
+
return {
|
|
32
|
+
signup: (input) => signup(input, config),
|
|
33
|
+
login: (input) => login(input, config),
|
|
34
|
+
refresh: (refreshToken) => refresh(refreshToken, config),
|
|
35
|
+
logout: (refreshToken) => logout(refreshToken, config),
|
|
36
|
+
logoutAll: (userId) => logoutAll(userId, config),
|
|
37
|
+
protect: () => protect(config),
|
|
38
|
+
authorize: (...roles) => authorize(...roles),
|
|
39
|
+
hashPassword: (plain) => hashPassword(plain, resolved.saltRounds),
|
|
40
|
+
verifyPassword: (plain, hash) => verifyPassword(plain, hash),
|
|
41
|
+
signAccessToken: (payload) => signAccessToken(payload, config),
|
|
42
|
+
signRefreshToken: (sessionId) => signRefreshToken(sessionId, config),
|
|
43
|
+
verifyAccessToken: (token) => verifyAccessToken(token, config),
|
|
44
|
+
verifyRefreshToken: (token) => verifyRefreshToken(token, config),
|
|
45
|
+
router: () => createAuthRouter(config),
|
|
46
|
+
permit: (optionsOrCheck) => permit(optionsOrCheck),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC9D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAC3G,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;AAClD,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAChD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AA+J1D;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,UAAU,CACxB,MAAyB;IAEzB,cAAc,CAAC,MAAoB,CAAC,CAAC;IACrC,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAoB,CAAC,CAAC;IAErD,OAAO;QACL,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAoB,EAAE,MAAoB,CAA+B;QACnG,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,EAAE,MAAoB,CAA+B;QAClF,OAAO,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,MAAoB,CAAkC;QACvG,MAAM,EAAE,CAAC,YAAY,EAAE,EAAE,CAAC,MAAM,CAAC,YAAY,EAAE,MAAoB,CAAC;QACpE,SAAS,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,MAAoB,CAAC;QAC9D,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAoB,CAAC;QAC5C,SAAS,EAAE,CAAC,GAAG,KAAK,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC;QAC5C,YAAY,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,YAAY,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC;QACjE,cAAc,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,IAAI,CAAC;QAC5D,eAAe,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,eAAe,CAAC,OAAmB,EAAE,MAAoB,CAAC;QACxF,gBAAgB,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,gBAAgB,CAAC,SAAS,EAAE,MAAoB,CAAC;QAClF,iBAAiB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,iBAAiB,CAAC,KAAK,EAAE,MAAoB,CAAoB;QAC/F,kBAAkB,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,kBAAkB,CAAC,KAAK,EAAE,MAAoB,CAAC;QAC9E,MAAM,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,MAAM,CAAC;QACtC,MAAM,EAAE,CAAC,cAAkD,EAAE,EAAE,CAAC,MAAM,CAAC,cAAc,CAAC;KACvF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discriminant codes for {@link AuthError}.
|
|
3
|
+
*
|
|
4
|
+
* - `INVALID_CREDENTIALS` — identifier or password did not match (intentionally vague to prevent user enumeration)
|
|
5
|
+
* - `USER_NOT_FOUND` — an operation required a user that does not exist
|
|
6
|
+
* - `USER_ALREADY_EXISTS` — signup was attempted with an identifier already in the database
|
|
7
|
+
* - `TOKEN_EXPIRED` — the JWT was valid but its `exp` claim is in the past
|
|
8
|
+
* - `TOKEN_INVALID` — the JWT could not be verified (bad signature, malformed, wrong type)
|
|
9
|
+
* - `FORBIDDEN` — the user is authenticated but lacks the required role
|
|
10
|
+
* - `UNAUTHORIZED` — no valid access token was present on the request
|
|
11
|
+
* - `INVALID_ROLE` — a role name was used that is not in `validRoles`
|
|
12
|
+
* - `VALIDATION_ERROR` — a required field was missing or had an invalid value
|
|
13
|
+
* - `CONFIGURATION_ERROR` — `createAuth` was called with an invalid configuration
|
|
14
|
+
*/
|
|
15
|
+
export type AuthErrorCode = 'INVALID_CREDENTIALS' | 'USER_NOT_FOUND' | 'USER_ALREADY_EXISTS' | 'TOKEN_EXPIRED' | 'TOKEN_INVALID' | 'FORBIDDEN' | 'UNAUTHORIZED' | 'INVALID_ROLE' | 'VALIDATION_ERROR' | 'CONFIGURATION_ERROR';
|
|
16
|
+
/**
|
|
17
|
+
* Error class thrown by the library for all authentication and authorization failures.
|
|
18
|
+
*
|
|
19
|
+
* Carries a machine-readable `code` that lets you distinguish error types without
|
|
20
|
+
* string-matching on the message:
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* import { AuthError } from 'rizzzdev-auth';
|
|
24
|
+
*
|
|
25
|
+
* app.use((err, req, res, next) => {
|
|
26
|
+
* if (err instanceof AuthError) {
|
|
27
|
+
* const status = err.code === 'UNAUTHORIZED' ? 401
|
|
28
|
+
* : err.code === 'FORBIDDEN' ? 403
|
|
29
|
+
* : 400;
|
|
30
|
+
* res.status(status).json({ error: err.code, message: err.message });
|
|
31
|
+
* } else {
|
|
32
|
+
* next(err);
|
|
33
|
+
* }
|
|
34
|
+
* });
|
|
35
|
+
*/
|
|
36
|
+
export declare class AuthError extends Error {
|
|
37
|
+
readonly code: AuthErrorCode;
|
|
38
|
+
constructor(code: AuthErrorCode, message: string);
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=AuthError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthError.d.ts","sourceRoot":"","sources":["../../src/errors/AuthError.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,aAAa,GACrB,qBAAqB,GACrB,gBAAgB,GAChB,qBAAqB,GACrB,eAAe,GACf,eAAe,GACf,WAAW,GACX,cAAc,GACd,cAAc,GACd,kBAAkB,GAClB,qBAAqB,CAAC;AAE1B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,SAAgB,IAAI,EAAE,aAAa,CAAC;gBAExB,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM;CAKjD"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error class thrown by the library for all authentication and authorization failures.
|
|
3
|
+
*
|
|
4
|
+
* Carries a machine-readable `code` that lets you distinguish error types without
|
|
5
|
+
* string-matching on the message:
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* import { AuthError } from 'rizzzdev-auth';
|
|
9
|
+
*
|
|
10
|
+
* app.use((err, req, res, next) => {
|
|
11
|
+
* if (err instanceof AuthError) {
|
|
12
|
+
* const status = err.code === 'UNAUTHORIZED' ? 401
|
|
13
|
+
* : err.code === 'FORBIDDEN' ? 403
|
|
14
|
+
* : 400;
|
|
15
|
+
* res.status(status).json({ error: err.code, message: err.message });
|
|
16
|
+
* } else {
|
|
17
|
+
* next(err);
|
|
18
|
+
* }
|
|
19
|
+
* });
|
|
20
|
+
*/
|
|
21
|
+
export class AuthError extends Error {
|
|
22
|
+
code;
|
|
23
|
+
constructor(code, message) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'AuthError';
|
|
26
|
+
this.code = code;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=AuthError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuthError.js","sourceRoot":"","sources":["../../src/errors/AuthError.ts"],"names":[],"mappings":"AA0BA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,OAAO,SAAU,SAAQ,KAAK;IAClB,IAAI,CAAgB;IAEpC,YAAY,IAAmB,EAAE,OAAe;QAC9C,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;IACnB,CAAC;CACF"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { AuthUser } from './types/auth.js';
|
|
2
|
+
declare global {
|
|
3
|
+
namespace Express {
|
|
4
|
+
interface Request {
|
|
5
|
+
user?: AuthUser;
|
|
6
|
+
}
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export type { AuthConfig, CookieConfig, AuthUser, AuthResult, RefreshResult, SignupInput, LoginInput, AuthAdapter, UserRecord, SessionRecord, CreateUserData, } from './types/auth.js';
|
|
10
|
+
export type { AuthErrorCode } from './errors/AuthError.js';
|
|
11
|
+
export type { AuthClient } from './client.js';
|
|
12
|
+
export { AuthError } from './errors/AuthError.js';
|
|
13
|
+
export { createAuth } from './client.js';
|
|
14
|
+
export type { PermitCheck, PermitOptions } from './middleware/permit.js';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhD,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,OAAO;YACf,IAAI,CAAC,EAAE,QAAQ,CAAC;SACjB;KACF;CACF;AAED,YAAY,EACV,UAAU,EACV,YAAY,EACZ,QAAQ,EACR,UAAU,EACV,aAAa,EACb,WAAW,EACX,UAAU,EACV,WAAW,EACX,UAAU,EACV,aAAa,EACb,cAAc,GACf,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AA4BA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { AuthAdapter, AuthConfig } from '../types/auth.js';
|
|
2
|
+
export interface ResolvedConfig {
|
|
3
|
+
secret: string;
|
|
4
|
+
accessExpiresIn: string | number;
|
|
5
|
+
refreshExpiresIn: string | number;
|
|
6
|
+
algorithm: 'HS256' | 'HS384' | 'HS512';
|
|
7
|
+
saltRounds: number;
|
|
8
|
+
validRoles: readonly string[];
|
|
9
|
+
adapter: AuthAdapter;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validates config at startup so misconfiguration is caught immediately,
|
|
13
|
+
* not at the first login attempt.
|
|
14
|
+
*/
|
|
15
|
+
export declare function validateConfig(config: AuthConfig): void;
|
|
16
|
+
export declare function resolveConfig(partial: AuthConfig): ResolvedConfig;
|
|
17
|
+
export declare function parseExpiry(expiresIn: string | number): number;
|
|
18
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/libs/config.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEhE,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,MAAM,GAAG,MAAM,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,MAAM,CAAC;IAClC,SAAS,EAAE,OAAO,GAAG,OAAO,GAAG,OAAO,CAAC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,OAAO,EAAE,WAAW,CAAC;CACtB;AAMD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CA0BvD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,UAAU,GAAG,cAAc,CAUjE;AAGD,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAkB9D"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { AuthError } from '../errors/AuthError.js';
|
|
2
|
+
const MIN_SECRET_LENGTH = 32;
|
|
3
|
+
const MIN_SALT_ROUNDS = 10;
|
|
4
|
+
const MAX_SALT_ROUNDS = 31;
|
|
5
|
+
/**
|
|
6
|
+
* Validates config at startup so misconfiguration is caught immediately,
|
|
7
|
+
* not at the first login attempt.
|
|
8
|
+
*/
|
|
9
|
+
export function validateConfig(config) {
|
|
10
|
+
if (!config.secret || config.secret.trim().length === 0) {
|
|
11
|
+
throw new AuthError('CONFIGURATION_ERROR', 'secret must not be empty');
|
|
12
|
+
}
|
|
13
|
+
if (config.secret.length < MIN_SECRET_LENGTH) {
|
|
14
|
+
throw new AuthError('CONFIGURATION_ERROR', `secret must be at least ${MIN_SECRET_LENGTH} characters to be cryptographically safe`);
|
|
15
|
+
}
|
|
16
|
+
const saltRounds = config.saltRounds ?? 12;
|
|
17
|
+
if (!Number.isInteger(saltRounds) || saltRounds < MIN_SALT_ROUNDS || saltRounds > MAX_SALT_ROUNDS) {
|
|
18
|
+
throw new AuthError('CONFIGURATION_ERROR', `saltRounds must be an integer between ${MIN_SALT_ROUNDS} and ${MAX_SALT_ROUNDS}`);
|
|
19
|
+
}
|
|
20
|
+
if (!config.validRoles || config.validRoles.length === 0) {
|
|
21
|
+
throw new AuthError('CONFIGURATION_ERROR', 'validRoles must contain at least one role');
|
|
22
|
+
}
|
|
23
|
+
if (!config.adapter) {
|
|
24
|
+
throw new AuthError('CONFIGURATION_ERROR', 'adapter is required');
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function resolveConfig(partial) {
|
|
28
|
+
return {
|
|
29
|
+
secret: partial.secret,
|
|
30
|
+
accessExpiresIn: partial.accessExpiresIn ?? '15m',
|
|
31
|
+
refreshExpiresIn: partial.refreshExpiresIn ?? '7d',
|
|
32
|
+
algorithm: partial.algorithm ?? 'HS256',
|
|
33
|
+
saltRounds: partial.saltRounds ?? 12,
|
|
34
|
+
validRoles: partial.validRoles,
|
|
35
|
+
adapter: partial.adapter,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Parses '15m', '7d', '1h', '30s', '2w' → milliseconds
|
|
39
|
+
export function parseExpiry(expiresIn) {
|
|
40
|
+
if (typeof expiresIn === 'number')
|
|
41
|
+
return expiresIn * 1000;
|
|
42
|
+
const multipliers = {
|
|
43
|
+
s: 1_000,
|
|
44
|
+
m: 60_000,
|
|
45
|
+
h: 3_600_000,
|
|
46
|
+
d: 86_400_000,
|
|
47
|
+
w: 604_800_000,
|
|
48
|
+
};
|
|
49
|
+
const match = /^(\d+)([smhdw])$/.exec(expiresIn);
|
|
50
|
+
if (!match?.[1] || !match?.[2]) {
|
|
51
|
+
throw new Error(`Invalid expiresIn: "${expiresIn}". Use e.g. "15m", "7d", "1h".`);
|
|
52
|
+
}
|
|
53
|
+
const unit = multipliers[match[2]];
|
|
54
|
+
if (unit === undefined) {
|
|
55
|
+
throw new Error(`Invalid expiresIn: "${expiresIn}". Use e.g. "15m", "7d", "1h".`);
|
|
56
|
+
}
|
|
57
|
+
return parseInt(match[1], 10) * unit;
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/libs/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAanD,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAC7B,MAAM,eAAe,GAAG,EAAE,CAAC;AAC3B,MAAM,eAAe,GAAG,EAAE,CAAC;AAE3B;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,MAAkB;IAC/C,IAAI,CAAC,MAAM,CAAC,MAAM,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACxD,MAAM,IAAI,SAAS,CAAC,qBAAqB,EAAE,0BAA0B,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,iBAAiB,EAAE,CAAC;QAC7C,MAAM,IAAI,SAAS,CACjB,qBAAqB,EACrB,2BAA2B,iBAAiB,0CAA0C,CACvF,CAAC;IACJ,CAAC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,EAAE,CAAC;IAC3C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,UAAU,CAAC,IAAI,UAAU,GAAG,eAAe,IAAI,UAAU,GAAG,eAAe,EAAE,CAAC;QAClG,MAAM,IAAI,SAAS,CACjB,qBAAqB,EACrB,yCAAyC,eAAe,QAAQ,eAAe,EAAE,CAClF,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzD,MAAM,IAAI,SAAS,CAAC,qBAAqB,EAAE,2CAA2C,CAAC,CAAC;IAC1F,CAAC;IAED,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,SAAS,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,CAAC;IACpE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAmB;IAC/C,OAAO;QACL,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,eAAe,EAAE,OAAO,CAAC,eAAe,IAAI,KAAK;QACjD,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,IAAI;QAClD,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,OAAO;QACvC,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,EAAE;QACpC,UAAU,EAAE,OAAO,CAAC,UAAU;QAC9B,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC;AACJ,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,WAAW,CAAC,SAA0B;IACpD,IAAI,OAAO,SAAS,KAAK,QAAQ;QAAE,OAAO,SAAS,GAAG,IAAI,CAAC;IAC3D,MAAM,WAAW,GAA2B;QAC1C,CAAC,EAAE,KAAK;QACR,CAAC,EAAE,MAAM;QACT,CAAC,EAAE,SAAS;QACZ,CAAC,EAAE,UAAU;QACb,CAAC,EAAE,WAAW;KACf,CAAC;IACF,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IACjD,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC/B,MAAM,IAAI,KAAK,CAAC,uBAAuB,SAAS,gCAAgC,CAAC,CAAC;IACpF,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CAAC,uBAAuB,SAAS,gCAAgC,CAAC,CAAC;IACpF,CAAC;IACD,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC;AACvC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/libs/hash.ts"],"names":[],"mappings":"AAEA,wBAAsB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,SAAK,GAAG,OAAO,CAAC,MAAM,CAAC,CAElF;AAED,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAElF"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import bcrypt from 'bcrypt';
|
|
2
|
+
export async function hashPassword(plain, saltRounds = 12) {
|
|
3
|
+
return bcrypt.hash(plain, saltRounds);
|
|
4
|
+
}
|
|
5
|
+
export async function verifyPassword(plain, hash) {
|
|
6
|
+
return bcrypt.compare(plain, hash);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=hash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/libs/hash.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,QAAQ,CAAC;AAE5B,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,KAAa,EAAE,UAAU,GAAG,EAAE;IAC/D,OAAO,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC;AACxC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAa,EAAE,IAAY;IAC9D,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AuthConfig, AuthUser } from '../types/auth.js';
|
|
2
|
+
export declare function signAccessToken(payload: AuthUser, config: AuthConfig): string;
|
|
3
|
+
export declare function signRefreshToken(sessionId: string, config: AuthConfig): string;
|
|
4
|
+
export declare function verifyAccessToken(token: string, config: AuthConfig): AuthUser;
|
|
5
|
+
export declare function verifyRefreshToken(token: string, config: AuthConfig): {
|
|
6
|
+
sessionId: string;
|
|
7
|
+
};
|
|
8
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../src/libs/token.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AA2C7D,wBAAgB,eAAe,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAI7E;AAED,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,MAAM,CAI9E;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,QAAQ,CAI7E;AAED,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,CAI3F"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import jwt, {} from 'jsonwebtoken';
|
|
2
|
+
import { AuthError } from '../errors/AuthError.js';
|
|
3
|
+
import { resolveConfig } from './config.js';
|
|
4
|
+
function deriveSecrets(secret) {
|
|
5
|
+
return {
|
|
6
|
+
access: `${secret}:access`,
|
|
7
|
+
refresh: `${secret}:refresh`,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function sign(payload, secret, expiresIn, algorithm) {
|
|
11
|
+
const options = {
|
|
12
|
+
expiresIn: expiresIn,
|
|
13
|
+
algorithm,
|
|
14
|
+
};
|
|
15
|
+
return jwt.sign(payload, secret, options);
|
|
16
|
+
}
|
|
17
|
+
function verify(token, secret, algorithm) {
|
|
18
|
+
try {
|
|
19
|
+
const decoded = jwt.verify(token, secret, { algorithms: [algorithm] });
|
|
20
|
+
if (typeof decoded === 'string' || decoded === null) {
|
|
21
|
+
throw new AuthError('TOKEN_INVALID', 'Token payload is not an object');
|
|
22
|
+
}
|
|
23
|
+
return decoded;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
if (err instanceof AuthError)
|
|
27
|
+
throw err;
|
|
28
|
+
if (err instanceof jwt.TokenExpiredError) {
|
|
29
|
+
throw new AuthError('TOKEN_EXPIRED', 'Token has expired');
|
|
30
|
+
}
|
|
31
|
+
throw new AuthError('TOKEN_INVALID', 'Token is invalid or malformed');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export function signAccessToken(payload, config) {
|
|
35
|
+
const resolved = resolveConfig(config);
|
|
36
|
+
const { access } = deriveSecrets(resolved.secret);
|
|
37
|
+
return sign(payload, access, resolved.accessExpiresIn, resolved.algorithm);
|
|
38
|
+
}
|
|
39
|
+
export function signRefreshToken(sessionId, config) {
|
|
40
|
+
const resolved = resolveConfig(config);
|
|
41
|
+
const { refresh } = deriveSecrets(resolved.secret);
|
|
42
|
+
return sign({ sessionId }, refresh, resolved.refreshExpiresIn, resolved.algorithm);
|
|
43
|
+
}
|
|
44
|
+
export function verifyAccessToken(token, config) {
|
|
45
|
+
const resolved = resolveConfig(config);
|
|
46
|
+
const { access } = deriveSecrets(resolved.secret);
|
|
47
|
+
return verify(token, access, resolved.algorithm);
|
|
48
|
+
}
|
|
49
|
+
export function verifyRefreshToken(token, config) {
|
|
50
|
+
const resolved = resolveConfig(config);
|
|
51
|
+
const { refresh } = deriveSecrets(resolved.secret);
|
|
52
|
+
return verify(token, refresh, resolved.algorithm);
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../src/libs/token.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.d.ts","sourceRoot":"","sources":["../../src/middleware/authorize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C,wBAAgB,SAAS,CAAC,KAAK,SAAS,MAAM,EAAE,GAAG,YAAY,EAAE,KAAK,EAAE,GAAG,cAAc,CAcxF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { AuthError } from '../errors/AuthError.js';
|
|
2
|
+
export function authorize(...allowedRoles) {
|
|
3
|
+
return (req, _res, next) => {
|
|
4
|
+
if (!req.user) {
|
|
5
|
+
return next(new AuthError('UNAUTHORIZED', 'Not authenticated'));
|
|
6
|
+
}
|
|
7
|
+
const userRoles = req.user.roles;
|
|
8
|
+
const hasRole = allowedRoles.some((role) => userRoles.includes(role));
|
|
9
|
+
if (!hasRole) {
|
|
10
|
+
return next(new AuthError('FORBIDDEN', `Requires one of roles: ${allowedRoles.join(', ')}`));
|
|
11
|
+
}
|
|
12
|
+
next();
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=authorize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"authorize.js","sourceRoot":"","sources":["../../src/middleware/authorize.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AAEnD,MAAM,UAAU,SAAS,CAAuB,GAAG,YAAqB;IACtE,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;QACzB,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACd,OAAO,IAAI,CAAC,IAAI,SAAS,CAAC,cAAc,EAAE,mBAAmB,CAAC,CAAC,CAAC;QAClE,CAAC;QACD,MAAM,SAAS,GAAsB,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC;QACpD,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;QACtE,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,IAAI,CACT,IAAI,SAAS,CAAC,WAAW,EAAE,0BAA0B,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAChF,CAAC;QACJ,CAAC;QACD,IAAI,EAAE,CAAC;IACT,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { Request, RequestHandler } from 'express';
|
|
2
|
+
/** A function that determines whether the current request is permitted. */
|
|
3
|
+
export type PermitCheck = (req: Request) => boolean | Promise<boolean>;
|
|
4
|
+
/**
|
|
5
|
+
* Options for {@link permit} when you need role-bypass alongside a resource check.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* // Admins can edit any post; others only their own
|
|
9
|
+
* auth.permit({
|
|
10
|
+
* roles: ['admin'],
|
|
11
|
+
* check: async (req) => {
|
|
12
|
+
* const post = await db.findPost(req.params['id']);
|
|
13
|
+
* return post?.authorId === req.user!.id;
|
|
14
|
+
* },
|
|
15
|
+
* })
|
|
16
|
+
*/
|
|
17
|
+
export interface PermitOptions<TRole extends string> {
|
|
18
|
+
/**
|
|
19
|
+
* Roles whose members are granted access without running `check`.
|
|
20
|
+
* Use for privileged roles like `'admin'` that should bypass ownership checks.
|
|
21
|
+
*/
|
|
22
|
+
roles?: TRole[];
|
|
23
|
+
/**
|
|
24
|
+
* Permission check executed when the user has none of the bypass `roles`.
|
|
25
|
+
* Return `true` to allow, `false` to deny with `FORBIDDEN`.
|
|
26
|
+
* May be async — useful for database-backed ownership checks.
|
|
27
|
+
*/
|
|
28
|
+
check: PermitCheck;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Express middleware factory for resource-level permission checks.
|
|
32
|
+
*
|
|
33
|
+
* Must be used **after** `protect()`. Evaluates a check function against the
|
|
34
|
+
* current request; calls `next(AuthError)` with code `FORBIDDEN` if it returns `false`.
|
|
35
|
+
*
|
|
36
|
+
* Accepts either a bare check function or an options object with an optional
|
|
37
|
+
* `roles` list whose members bypass the check entirely.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Simple ownership check
|
|
41
|
+
* router.put('/users/:id',
|
|
42
|
+
* auth.protect(),
|
|
43
|
+
* auth.permit((req) => req.user!.id === req.params['id']),
|
|
44
|
+
* updateUserHandler,
|
|
45
|
+
* );
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* // Admins bypass the check; others must own the post
|
|
49
|
+
* router.delete('/posts/:id',
|
|
50
|
+
* auth.protect(),
|
|
51
|
+
* auth.permit({
|
|
52
|
+
* roles: ['admin'],
|
|
53
|
+
* check: async (req) => {
|
|
54
|
+
* const post = await db.post.findUnique({ where: { id: req.params['id'] } });
|
|
55
|
+
* return post?.authorId === req.user!.id;
|
|
56
|
+
* },
|
|
57
|
+
* }),
|
|
58
|
+
* deletePostHandler,
|
|
59
|
+
* );
|
|
60
|
+
*/
|
|
61
|
+
export declare function permit<TRole extends string>(optionsOrCheck: PermitOptions<TRole> | PermitCheck): RequestHandler;
|
|
62
|
+
//# sourceMappingURL=permit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"permit.d.ts","sourceRoot":"","sources":["../../src/middleware/permit.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAGvD,2EAA2E;AAC3E,MAAM,MAAM,WAAW,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAEvE;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,aAAa,CAAC,KAAK,SAAS,MAAM;IACjD;;;OAGG;IACH,KAAK,CAAC,EAAE,KAAK,EAAE,CAAC;IAChB;;;;OAIG;IACH,KAAK,EAAE,WAAW,CAAC;CACpB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,MAAM,CAAC,KAAK,SAAS,MAAM,EACzC,cAAc,EAAE,aAAa,CAAC,KAAK,CAAC,GAAG,WAAW,GACjD,cAAc,CA4BhB"}
|