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.
Files changed (50) hide show
  1. package/README.md +1044 -0
  2. package/dist/client.d.ts +158 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +49 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/errors/AuthError.d.ts +40 -0
  7. package/dist/errors/AuthError.d.ts.map +1 -0
  8. package/dist/errors/AuthError.js +29 -0
  9. package/dist/errors/AuthError.js.map +1 -0
  10. package/dist/index.d.ts +15 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +3 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/libs/config.d.ts +18 -0
  15. package/dist/libs/config.d.ts.map +1 -0
  16. package/dist/libs/config.js +59 -0
  17. package/dist/libs/config.js.map +1 -0
  18. package/dist/libs/hash.d.ts +3 -0
  19. package/dist/libs/hash.d.ts.map +1 -0
  20. package/dist/libs/hash.js +8 -0
  21. package/dist/libs/hash.js.map +1 -0
  22. package/dist/libs/token.d.ts +8 -0
  23. package/dist/libs/token.d.ts.map +1 -0
  24. package/dist/libs/token.js +54 -0
  25. package/dist/libs/token.js.map +1 -0
  26. package/dist/middleware/authorize.d.ts +3 -0
  27. package/dist/middleware/authorize.d.ts.map +1 -0
  28. package/dist/middleware/authorize.js +15 -0
  29. package/dist/middleware/authorize.js.map +1 -0
  30. package/dist/middleware/permit.d.ts +62 -0
  31. package/dist/middleware/permit.d.ts.map +1 -0
  32. package/dist/middleware/permit.js +61 -0
  33. package/dist/middleware/permit.js.map +1 -0
  34. package/dist/middleware/protect.d.ts +4 -0
  35. package/dist/middleware/protect.d.ts.map +1 -0
  36. package/dist/middleware/protect.js +19 -0
  37. package/dist/middleware/protect.js.map +1 -0
  38. package/dist/middleware/router.d.ts +27 -0
  39. package/dist/middleware/router.d.ts.map +1 -0
  40. package/dist/middleware/router.js +244 -0
  41. package/dist/middleware/router.js.map +1 -0
  42. package/dist/services/auth.d.ts +7 -0
  43. package/dist/services/auth.d.ts.map +1 -0
  44. package/dist/services/auth.js +84 -0
  45. package/dist/services/auth.js.map +1 -0
  46. package/dist/types/auth.d.ts +234 -0
  47. package/dist/types/auth.d.ts.map +1 -0
  48. package/dist/types/auth.js +2 -0
  49. package/dist/types/auth.js.map +1 -0
  50. package/package.json +38 -0
@@ -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"}
@@ -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,3 @@
1
+ export { AuthError } from './errors/AuthError.js';
2
+ export { createAuth } from './client.js';
3
+ //# sourceMappingURL=index.js.map
@@ -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,3 @@
1
+ export declare function hashPassword(plain: string, saltRounds?: number): Promise<string>;
2
+ export declare function verifyPassword(plain: string, hash: string): Promise<boolean>;
3
+ //# sourceMappingURL=hash.d.ts.map
@@ -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,3 @@
1
+ import type { RequestHandler } from 'express';
2
+ export declare function authorize<TRole extends string>(...allowedRoles: TRole[]): RequestHandler;
3
+ //# sourceMappingURL=authorize.d.ts.map
@@ -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"}