sentri 1.1.2 → 2.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 +325 -181
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +10 -103
- package/dist/index.d.ts +1046 -11
- package/dist/index.js +1 -5
- package/package.json +13 -6
- package/templates/drizzle/auth.ts +47 -4
- package/templates/prisma/auth.ts +47 -4
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/client.d.ts +0 -160
- package/dist/client.d.ts.map +0 -1
- package/dist/client.js +0 -45
- package/dist/client.js.map +0 -1
- package/dist/errors/AuthError.d.ts +0 -99
- package/dist/errors/AuthError.d.ts.map +0 -1
- package/dist/errors/AuthError.js +0 -97
- package/dist/errors/AuthError.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/libs/config.d.ts +0 -62
- package/dist/libs/config.d.ts.map +0 -1
- package/dist/libs/config.js +0 -97
- package/dist/libs/config.js.map +0 -1
- package/dist/libs/hash.d.ts +0 -17
- package/dist/libs/hash.d.ts.map +0 -1
- package/dist/libs/hash.js +0 -22
- package/dist/libs/hash.js.map +0 -1
- package/dist/libs/token.d.ts +0 -46
- package/dist/libs/token.d.ts.map +0 -1
- package/dist/libs/token.js +0 -118
- package/dist/libs/token.js.map +0 -1
- package/dist/middleware/authorize.d.ts +0 -18
- package/dist/middleware/authorize.d.ts.map +0 -1
- package/dist/middleware/authorize.js +0 -30
- package/dist/middleware/authorize.js.map +0 -1
- package/dist/middleware/errorHandler.d.ts +0 -71
- package/dist/middleware/errorHandler.d.ts.map +0 -1
- package/dist/middleware/errorHandler.js +0 -74
- package/dist/middleware/errorHandler.js.map +0 -1
- package/dist/middleware/permit.d.ts +0 -62
- package/dist/middleware/permit.d.ts.map +0 -1
- package/dist/middleware/permit.js +0 -61
- package/dist/middleware/permit.js.map +0 -1
- package/dist/middleware/protect.d.ts +0 -31
- package/dist/middleware/protect.d.ts.map +0 -1
- package/dist/middleware/protect.js +0 -54
- package/dist/middleware/protect.js.map +0 -1
- package/dist/middleware/router.d.ts +0 -34
- package/dist/middleware/router.d.ts.map +0 -1
- package/dist/middleware/router.js +0 -264
- package/dist/middleware/router.js.map +0 -1
- package/dist/services/auth.d.ts +0 -85
- package/dist/services/auth.d.ts.map +0 -1
- package/dist/services/auth.js +0 -173
- package/dist/services/auth.js.map +0 -1
- package/dist/types/auth.d.ts +0 -450
- package/dist/types/auth.d.ts.map +0 -1
- package/dist/types/auth.js +0 -21
- package/dist/types/auth.js.map +0 -1
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { SentriError } from '../errors/AuthError.js';
|
|
2
|
-
/**
|
|
3
|
-
* Creates an Express error-handling middleware that formats every `SentriError`
|
|
4
|
-
* (including subclasses) into the standard sentri response envelope:
|
|
5
|
-
*
|
|
6
|
-
* ```json
|
|
7
|
-
* { "error": true, "statusCode": 401, "code": "UNAUTHORIZED", "message": "...", "data": null }
|
|
8
|
-
* ```
|
|
9
|
-
*
|
|
10
|
-
* Prefer using `auth.errorHandler()` instead of calling this directly:
|
|
11
|
-
*
|
|
12
|
-
* ```typescript
|
|
13
|
-
* app.use('/auth', auth.router());
|
|
14
|
-
* app.use('/api', apiRouter);
|
|
15
|
-
*
|
|
16
|
-
* // Must come after all route/middleware registrations
|
|
17
|
-
* app.use(auth.errorHandler());
|
|
18
|
-
* ```
|
|
19
|
-
*
|
|
20
|
-
* ---
|
|
21
|
-
*
|
|
22
|
-
* **Works with built-in sentri errors and your own subclasses**
|
|
23
|
-
*
|
|
24
|
-
* Because `instanceof SentriError` matches any subclass, you can define
|
|
25
|
-
* application-specific error types and have them automatically formatted
|
|
26
|
-
* by this handler:
|
|
27
|
-
*
|
|
28
|
-
* ```typescript
|
|
29
|
-
* import { SentriError } from 'sentri';
|
|
30
|
-
*
|
|
31
|
-
* // Extend SentriError for domain-specific failures
|
|
32
|
-
* export class NotFoundError extends SentriError {
|
|
33
|
-
* constructor(resource: string) {
|
|
34
|
-
* super('NOT_FOUND', `${resource} not found`, 404);
|
|
35
|
-
* }
|
|
36
|
-
* }
|
|
37
|
-
*
|
|
38
|
-
* export class PaymentError extends SentriError {
|
|
39
|
-
* constructor(message: string) {
|
|
40
|
-
* super('PAYMENT_FAILED', message, 402);
|
|
41
|
-
* }
|
|
42
|
-
* }
|
|
43
|
-
*
|
|
44
|
-
* // All of the above are caught and formatted by one handler
|
|
45
|
-
* app.use(auth.errorHandler({
|
|
46
|
-
* onUnhandled: (err) => console.error('Unexpected error:', err),
|
|
47
|
-
* }));
|
|
48
|
-
* ```
|
|
49
|
-
*
|
|
50
|
-
* @param options - Optional configuration (see {@link ErrorHandlerOptions}).
|
|
51
|
-
* @returns An Express `ErrorRequestHandler` (4-argument middleware).
|
|
52
|
-
*/
|
|
53
|
-
export function createErrorHandler(options) {
|
|
54
|
-
return (err, _req, res, _next) => {
|
|
55
|
-
if (err instanceof SentriError) {
|
|
56
|
-
res.status(err.statusCode).json({
|
|
57
|
-
error: true,
|
|
58
|
-
statusCode: err.statusCode,
|
|
59
|
-
code: err.code,
|
|
60
|
-
message: err.message,
|
|
61
|
-
data: null,
|
|
62
|
-
});
|
|
63
|
-
return;
|
|
64
|
-
}
|
|
65
|
-
options?.onUnhandled?.(err);
|
|
66
|
-
res.status(500).json({
|
|
67
|
-
error: true,
|
|
68
|
-
statusCode: 500,
|
|
69
|
-
message: 'Internal server error',
|
|
70
|
-
data: null,
|
|
71
|
-
});
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
//# sourceMappingURL=errorHandler.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/middleware/errorHandler.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAoBrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkDG;AACH,MAAM,UAAU,kBAAkB,CAAC,OAA6B;IAC9D,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QAC/B,IAAI,GAAG,YAAY,WAAW,EAAE,CAAC;YAC/B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC;gBAC9B,KAAK,EAAE,IAAI;gBACX,UAAU,EAAE,GAAG,CAAC,UAAU;gBAC1B,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,OAAO,EAAE,GAAG,CAAC,OAAO;gBACpB,IAAI,EAAE,IAAI;aACX,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,OAAO,EAAE,WAAW,EAAE,CAAC,GAAG,CAAC,CAAC;QAE5B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC;YACnB,KAAK,EAAE,IAAI;YACX,UAAU,EAAE,GAAG;YACf,OAAO,EAAE,uBAAuB;YAChC,IAAI,EAAE,IAAI;SACX,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import type { Request, RequestHandler } from 'express';
|
|
2
|
-
/** A function that determines whether the current request is permitted. */
|
|
3
|
-
export type PermitCheck = (request: 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 (request) => {
|
|
12
|
-
* const post = await db.findPost(request.params['id']);
|
|
13
|
-
* return post?.authorId === request.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(SentriError)` 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((request) => request.user!.id === request.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 (request) => {
|
|
54
|
-
* const post = await db.post.findUnique({ where: { id: request.params['id'] } });
|
|
55
|
-
* return post?.authorId === request.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
|
|
@@ -1 +0,0 @@
|
|
|
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,OAAO,EAAE,OAAO,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;AAE3E;;;;;;;;;;;;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"}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { SentriError } from '../errors/AuthError.js';
|
|
2
|
-
/**
|
|
3
|
-
* Express middleware factory for resource-level permission checks.
|
|
4
|
-
*
|
|
5
|
-
* Must be used **after** `protect()`. Evaluates a check function against the
|
|
6
|
-
* current request; calls `next(SentriError)` with code `FORBIDDEN` if it returns `false`.
|
|
7
|
-
*
|
|
8
|
-
* Accepts either a bare check function or an options object with an optional
|
|
9
|
-
* `roles` list whose members bypass the check entirely.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* // Simple ownership check
|
|
13
|
-
* router.put('/users/:id',
|
|
14
|
-
* auth.protect(),
|
|
15
|
-
* auth.permit((request) => request.user!.id === request.params['id']),
|
|
16
|
-
* updateUserHandler,
|
|
17
|
-
* );
|
|
18
|
-
*
|
|
19
|
-
* @example
|
|
20
|
-
* // Admins bypass the check; others must own the post
|
|
21
|
-
* router.delete('/posts/:id',
|
|
22
|
-
* auth.protect(),
|
|
23
|
-
* auth.permit({
|
|
24
|
-
* roles: ['admin'],
|
|
25
|
-
* check: async (request) => {
|
|
26
|
-
* const post = await db.post.findUnique({ where: { id: request.params['id'] } });
|
|
27
|
-
* return post?.authorId === request.user!.id;
|
|
28
|
-
* },
|
|
29
|
-
* }),
|
|
30
|
-
* deletePostHandler,
|
|
31
|
-
* );
|
|
32
|
-
*/
|
|
33
|
-
export function permit(optionsOrCheck) {
|
|
34
|
-
const options = typeof optionsOrCheck === 'function'
|
|
35
|
-
? { check: optionsOrCheck }
|
|
36
|
-
: optionsOrCheck;
|
|
37
|
-
return async (request, _response, next) => {
|
|
38
|
-
if (!request.user) {
|
|
39
|
-
return next(new SentriError('UNAUTHORIZED', 'Not authenticated'));
|
|
40
|
-
}
|
|
41
|
-
if (options.roles && options.roles.length > 0) {
|
|
42
|
-
const userRoles = request.user.roles;
|
|
43
|
-
const hasBypassRole = options.roles.some((role) => userRoles.includes(role));
|
|
44
|
-
if (hasBypassRole)
|
|
45
|
-
return next();
|
|
46
|
-
}
|
|
47
|
-
try {
|
|
48
|
-
const allowed = await options.check(request);
|
|
49
|
-
if (allowed) {
|
|
50
|
-
next();
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
next(new SentriError('FORBIDDEN', 'You do not have permission to perform this action'));
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
next(error);
|
|
58
|
-
}
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
//# sourceMappingURL=permit.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"permit.js","sourceRoot":"","sources":["../../src/middleware/permit.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAgCrD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,MAAM,CACpB,cAAkD;IAElD,MAAM,OAAO,GACX,OAAO,cAAc,KAAK,UAAU;QAClC,CAAC,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE;QAC3B,CAAC,CAAC,cAAc,CAAC;IAErB,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE;QACxC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;YAClB,OAAO,IAAI,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,mBAAmB,CAAC,CAAC,CAAC;QACpE,CAAC;QAED,IAAI,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC9C,MAAM,SAAS,GAAsB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC;YACxD,MAAM,aAAa,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;YAC7E,IAAI,aAAa;gBAAE,OAAO,IAAI,EAAE,CAAC;QACnC,CAAC;QAED,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7C,IAAI,OAAO,EAAE,CAAC;gBACZ,IAAI,EAAE,CAAC;YACT,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,IAAI,WAAW,CAAC,WAAW,EAAE,mDAAmD,CAAC,CAAC,CAAC;YAC1F,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { RequestHandler } from 'express';
|
|
2
|
-
import type { AuthConfig } from '../types/auth.js';
|
|
3
|
-
/**
|
|
4
|
-
* Express middleware factory that enforces JWT authentication and session validity.
|
|
5
|
-
*
|
|
6
|
-
* Reads the `Authorization: Bearer <token>` header, verifies the access token,
|
|
7
|
-
* and attaches the decoded payload to `req.user`. Calls `next(SentriError)` on
|
|
8
|
-
* any failure so your error handler can convert it to an HTTP response.
|
|
9
|
-
*
|
|
10
|
-
* Since sentri 1.1.0 access tokens embed a `sessionId` claim. When this claim
|
|
11
|
-
* is present, `protect()` performs a lightweight database lookup
|
|
12
|
-
* (`adapter.session.findById`) to confirm the session is still active. This
|
|
13
|
-
* means a user who has logged out (or been logged out from all devices) cannot
|
|
14
|
-
* use an access token that was issued before the logout — even if the token has
|
|
15
|
-
* not yet expired.
|
|
16
|
-
*
|
|
17
|
-
* Tokens issued before 1.1.0 (without the `sessionId` claim) are still accepted
|
|
18
|
-
* but bypass the session check.
|
|
19
|
-
*
|
|
20
|
-
* Must be used **before** `authorize()` or `permit()`.
|
|
21
|
-
*
|
|
22
|
-
* @param config - Auth configuration (secret, algorithm, adapter).
|
|
23
|
-
* @returns An Express `RequestHandler` that populates `req.user` on success.
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* router.get('/profile', protect(config), (request, response) => {
|
|
27
|
-
* response.json(request.user);
|
|
28
|
-
* });
|
|
29
|
-
*/
|
|
30
|
-
export declare function protect(config: AuthConfig): RequestHandler;
|
|
31
|
-
//# sourceMappingURL=protect.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"protect.d.ts","sourceRoot":"","sources":["../../src/middleware/protect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAG9C,OAAO,KAAK,EAAsB,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEvE;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,UAAU,GAAG,cAAc,CAwB1D"}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { SentriError } from '../errors/AuthError.js';
|
|
2
|
-
import { verifyAccessToken } from '../libs/token.js';
|
|
3
|
-
/**
|
|
4
|
-
* Express middleware factory that enforces JWT authentication and session validity.
|
|
5
|
-
*
|
|
6
|
-
* Reads the `Authorization: Bearer <token>` header, verifies the access token,
|
|
7
|
-
* and attaches the decoded payload to `req.user`. Calls `next(SentriError)` on
|
|
8
|
-
* any failure so your error handler can convert it to an HTTP response.
|
|
9
|
-
*
|
|
10
|
-
* Since sentri 1.1.0 access tokens embed a `sessionId` claim. When this claim
|
|
11
|
-
* is present, `protect()` performs a lightweight database lookup
|
|
12
|
-
* (`adapter.session.findById`) to confirm the session is still active. This
|
|
13
|
-
* means a user who has logged out (or been logged out from all devices) cannot
|
|
14
|
-
* use an access token that was issued before the logout — even if the token has
|
|
15
|
-
* not yet expired.
|
|
16
|
-
*
|
|
17
|
-
* Tokens issued before 1.1.0 (without the `sessionId` claim) are still accepted
|
|
18
|
-
* but bypass the session check.
|
|
19
|
-
*
|
|
20
|
-
* Must be used **before** `authorize()` or `permit()`.
|
|
21
|
-
*
|
|
22
|
-
* @param config - Auth configuration (secret, algorithm, adapter).
|
|
23
|
-
* @returns An Express `RequestHandler` that populates `req.user` on success.
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* router.get('/profile', protect(config), (request, response) => {
|
|
27
|
-
* response.json(request.user);
|
|
28
|
-
* });
|
|
29
|
-
*/
|
|
30
|
-
export function protect(config) {
|
|
31
|
-
return async (request, _response, next) => {
|
|
32
|
-
const authHeader = request.headers['authorization'];
|
|
33
|
-
if (!authHeader?.startsWith('Bearer ')) {
|
|
34
|
-
return next(new SentriError('UNAUTHORIZED', 'Missing or malformed Authorization header'));
|
|
35
|
-
}
|
|
36
|
-
const token = authHeader.slice(7);
|
|
37
|
-
try {
|
|
38
|
-
const payload = verifyAccessToken(token, config);
|
|
39
|
-
request.user = { id: payload.id, identifier: payload.identifier, roles: payload.roles };
|
|
40
|
-
// Session-bound validation: reject the request if the session was revoked (logout).
|
|
41
|
-
if (payload.sessionId) {
|
|
42
|
-
const session = await config.adapter.session.findById(payload.sessionId);
|
|
43
|
-
if (!session) {
|
|
44
|
-
return next(new SentriError('UNAUTHORIZED', 'Session has been revoked'));
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
next();
|
|
48
|
-
}
|
|
49
|
-
catch (error) {
|
|
50
|
-
next(error);
|
|
51
|
-
}
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
//# sourceMappingURL=protect.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"protect.js","sourceRoot":"","sources":["../../src/middleware/protect.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAGrD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,UAAU,OAAO,CAAC,MAAkB;IACxC,OAAO,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE;QACxC,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC;QACpD,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACvC,OAAO,IAAI,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,2CAA2C,CAAC,CAAC,CAAC;QAC5F,CAAC;QACD,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,CAAC;YACH,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,EAAE,MAAM,CAAuB,CAAC;YACvE,OAAO,CAAC,IAAI,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC;YAExF,oFAAoF;YACpF,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;gBACtB,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACzE,IAAI,CAAC,OAAO,EAAE,CAAC;oBACb,OAAO,IAAI,CAAC,IAAI,WAAW,CAAC,cAAc,EAAE,0BAA0B,CAAC,CAAC,CAAC;gBAC3E,CAAC;YACH,CAAC;YAED,IAAI,EAAE,CAAC;QACT,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,KAAK,CAAC,CAAC;QACd,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import type { AuthConfig } from '../types/auth.js';
|
|
3
|
-
/**
|
|
4
|
-
* Creates a pre-built Express Router with all standard auth endpoints.
|
|
5
|
-
*
|
|
6
|
-
* Mount it once and all routes are ready:
|
|
7
|
-
*
|
|
8
|
-
* ```
|
|
9
|
-
* POST /register — register a new user (protected by X-Api-Key when config.apiKey is set)
|
|
10
|
-
* POST /login — authenticate and get tokens
|
|
11
|
-
* POST /refresh — rotate refresh token
|
|
12
|
-
* POST /logout — invalidate current session; access tokens issued before logout become invalid
|
|
13
|
-
* POST /logout-all — invalidate all sessions for the authenticated user
|
|
14
|
-
* GET /me — return the currently authenticated user
|
|
15
|
-
* POST /users/:userId/roles — assign roles (admin only)
|
|
16
|
-
* ```
|
|
17
|
-
*
|
|
18
|
-
* Requires `express.json()` to be applied before the router.
|
|
19
|
-
*
|
|
20
|
-
* When `cookie` is set in config, the refresh token is stored in an httpOnly
|
|
21
|
-
* cookie automatically — no `cookie-parser` needed.
|
|
22
|
-
*
|
|
23
|
-
* When `apiKey` is set in config, `POST /register` requires the caller to send
|
|
24
|
-
* an `X-Api-Key: <key>` header matching the configured value.
|
|
25
|
-
*
|
|
26
|
-
* When `router` is set in config, individual service functions can be replaced
|
|
27
|
-
* while the router still handles validation and response formatting.
|
|
28
|
-
*
|
|
29
|
-
* @example
|
|
30
|
-
* app.use(express.json());
|
|
31
|
-
* app.use('/auth', auth.router());
|
|
32
|
-
*/
|
|
33
|
-
export declare function createAuthRouter<TRole extends string>(config: AuthConfig<TRole>): Router;
|
|
34
|
-
//# sourceMappingURL=router.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"router.d.ts","sourceRoot":"","sources":["../../src/middleware/router.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAkD,MAAM,SAAS,CAAC;AAEjF,OAAO,KAAK,EAAE,UAAU,EAA6B,MAAM,kBAAkB,CAAC;AA4E9E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,MAAM,CAwLxF"}
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import { Router } from 'express';
|
|
2
|
-
import { SentriError } from '../errors/AuthError.js';
|
|
3
|
-
import { register, login, refresh, logout, logoutAll, assignRoles } from '../services/auth.js';
|
|
4
|
-
import { resolveConfig, parseExpiry } from '../libs/config.js';
|
|
5
|
-
import { protect } from './protect.js';
|
|
6
|
-
import { authorize } from './authorize.js';
|
|
7
|
-
const MIN_PASSWORD_LENGTH = 8;
|
|
8
|
-
// bcrypt silently truncates input beyond 72 bytes. Enforcing a cap makes the
|
|
9
|
-
// truncation boundary explicit so two passwords that share the same first 72
|
|
10
|
-
// bytes cannot be treated as identical.
|
|
11
|
-
const MAX_PASSWORD_LENGTH = 72;
|
|
12
|
-
const MAX_IDENTIFIER_LENGTH = 255;
|
|
13
|
-
function badRequest(message) {
|
|
14
|
-
return new SentriError('VALIDATION_ERROR', message);
|
|
15
|
-
}
|
|
16
|
-
function ok(response, statusCode, message, data) {
|
|
17
|
-
response.status(statusCode).json({ error: false, statusCode, message, data });
|
|
18
|
-
}
|
|
19
|
-
function fail(response, error) {
|
|
20
|
-
response.status(error.statusCode).json({ error: true, statusCode: error.statusCode, message: error.message, data: null });
|
|
21
|
-
}
|
|
22
|
-
function parseBody(body) {
|
|
23
|
-
if (body === null || body === undefined || typeof body !== 'object' || Array.isArray(body)) {
|
|
24
|
-
throw new SentriError('VALIDATION_ERROR', 'Request body is missing or not a JSON object. Did you apply express.json()?');
|
|
25
|
-
}
|
|
26
|
-
return body;
|
|
27
|
-
}
|
|
28
|
-
// Read a single cookie from the raw Cookie header — no cookie-parser needed.
|
|
29
|
-
function readCookie(cookieHeader, name) {
|
|
30
|
-
if (!cookieHeader)
|
|
31
|
-
return undefined;
|
|
32
|
-
const pair = cookieHeader
|
|
33
|
-
.split(';')
|
|
34
|
-
.map((segment) => segment.trim())
|
|
35
|
-
.find((segment) => segment.startsWith(`${name}=`));
|
|
36
|
-
return pair !== undefined ? pair.slice(name.length + 1) : undefined;
|
|
37
|
-
}
|
|
38
|
-
function getCookieName(config) {
|
|
39
|
-
return config.cookie?.name ?? 'refresh_token';
|
|
40
|
-
}
|
|
41
|
-
function setCookie(response, token, config) {
|
|
42
|
-
const cookieConfig = config.cookie ?? {};
|
|
43
|
-
const resolved = resolveConfig(config);
|
|
44
|
-
const maxAge = parseExpiry(resolved.refreshExpiresIn);
|
|
45
|
-
response.cookie(getCookieName(config), token, {
|
|
46
|
-
httpOnly: cookieConfig.httpOnly ?? true,
|
|
47
|
-
secure: cookieConfig.secure ?? false,
|
|
48
|
-
sameSite: cookieConfig.sameSite ?? 'strict',
|
|
49
|
-
path: cookieConfig.path ?? '/',
|
|
50
|
-
maxAge,
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
function clearCookie(response, config) {
|
|
54
|
-
const cookieConfig = config.cookie ?? {};
|
|
55
|
-
response.clearCookie(getCookieName(config), { path: cookieConfig.path ?? '/' });
|
|
56
|
-
}
|
|
57
|
-
/**
|
|
58
|
-
* Validate the `X-Api-Key` header when `config.apiKey` is set.
|
|
59
|
-
* Throws `SentriError` with code `UNAUTHORIZED` on mismatch.
|
|
60
|
-
*/
|
|
61
|
-
function validateApiKey(request, config) {
|
|
62
|
-
if (!config.apiKey)
|
|
63
|
-
return;
|
|
64
|
-
const provided = request.headers['x-api-key'];
|
|
65
|
-
if (typeof provided !== 'string' || provided !== config.apiKey) {
|
|
66
|
-
throw new SentriError('UNAUTHORIZED', 'Invalid or missing API key');
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Creates a pre-built Express Router with all standard auth endpoints.
|
|
71
|
-
*
|
|
72
|
-
* Mount it once and all routes are ready:
|
|
73
|
-
*
|
|
74
|
-
* ```
|
|
75
|
-
* POST /register — register a new user (protected by X-Api-Key when config.apiKey is set)
|
|
76
|
-
* POST /login — authenticate and get tokens
|
|
77
|
-
* POST /refresh — rotate refresh token
|
|
78
|
-
* POST /logout — invalidate current session; access tokens issued before logout become invalid
|
|
79
|
-
* POST /logout-all — invalidate all sessions for the authenticated user
|
|
80
|
-
* GET /me — return the currently authenticated user
|
|
81
|
-
* POST /users/:userId/roles — assign roles (admin only)
|
|
82
|
-
* ```
|
|
83
|
-
*
|
|
84
|
-
* Requires `express.json()` to be applied before the router.
|
|
85
|
-
*
|
|
86
|
-
* When `cookie` is set in config, the refresh token is stored in an httpOnly
|
|
87
|
-
* cookie automatically — no `cookie-parser` needed.
|
|
88
|
-
*
|
|
89
|
-
* When `apiKey` is set in config, `POST /register` requires the caller to send
|
|
90
|
-
* an `X-Api-Key: <key>` header matching the configured value.
|
|
91
|
-
*
|
|
92
|
-
* When `router` is set in config, individual service functions can be replaced
|
|
93
|
-
* while the router still handles validation and response formatting.
|
|
94
|
-
*
|
|
95
|
-
* @example
|
|
96
|
-
* app.use(express.json());
|
|
97
|
-
* app.use('/auth', auth.router());
|
|
98
|
-
*/
|
|
99
|
-
export function createAuthRouter(config) {
|
|
100
|
-
const router = Router();
|
|
101
|
-
// Resolve service functions — use custom override from config.router when provided, else fall back to the built-in service.
|
|
102
|
-
const baseConfig = config;
|
|
103
|
-
const registerFn = config.router?.register ?? ((input) => register(input, baseConfig));
|
|
104
|
-
const loginFn = config.router?.login ?? ((input) => login(input, baseConfig));
|
|
105
|
-
const refreshFn = config.router?.refresh ?? ((token) => refresh(token, baseConfig));
|
|
106
|
-
const logoutFn = config.router?.logout ?? ((token) => token !== undefined ? logout(token, baseConfig) : Promise.resolve());
|
|
107
|
-
const logoutAllFn = config.router?.logoutAll ?? ((userId) => logoutAll(userId, baseConfig));
|
|
108
|
-
const assignRolesFn = config.router?.assignRoles ?? ((userId, roles) => assignRoles(userId, roles, baseConfig));
|
|
109
|
-
/**
|
|
110
|
-
* POST /register
|
|
111
|
-
*
|
|
112
|
-
* Register a new user. Does **not** issue tokens — call `/login` after registration.
|
|
113
|
-
*
|
|
114
|
-
* When `config.apiKey` is set the caller must supply the matching value in the
|
|
115
|
-
* `X-Api-Key` header, preventing arbitrary users from self-registering as admins.
|
|
116
|
-
*/
|
|
117
|
-
router.post('/register', async (request, response, next) => {
|
|
118
|
-
try {
|
|
119
|
-
validateApiKey(request, config);
|
|
120
|
-
const body = parseBody(request.body);
|
|
121
|
-
const { identifier, password, roles } = body;
|
|
122
|
-
if (typeof identifier !== 'string' || identifier.trim().length === 0) {
|
|
123
|
-
throw badRequest('identifier is required and must be a non-empty string');
|
|
124
|
-
}
|
|
125
|
-
if (identifier.length > MAX_IDENTIFIER_LENGTH) {
|
|
126
|
-
throw badRequest(`identifier must not exceed ${MAX_IDENTIFIER_LENGTH} characters`);
|
|
127
|
-
}
|
|
128
|
-
if (typeof password !== 'string' || password.length < MIN_PASSWORD_LENGTH) {
|
|
129
|
-
throw badRequest(`password is required and must be at least ${MIN_PASSWORD_LENGTH} characters`);
|
|
130
|
-
}
|
|
131
|
-
if (password.length > MAX_PASSWORD_LENGTH) {
|
|
132
|
-
throw badRequest(`password must not exceed ${MAX_PASSWORD_LENGTH} characters`);
|
|
133
|
-
}
|
|
134
|
-
if (roles !== undefined && !Array.isArray(roles)) {
|
|
135
|
-
throw badRequest('roles must be an array of strings when provided');
|
|
136
|
-
}
|
|
137
|
-
if (Array.isArray(roles) && !roles.every((role) => typeof role === 'string')) {
|
|
138
|
-
throw badRequest('each role must be a string');
|
|
139
|
-
}
|
|
140
|
-
const rolesInput = Array.isArray(roles) ? roles : undefined;
|
|
141
|
-
const input = rolesInput !== undefined
|
|
142
|
-
? { identifier: identifier.trim(), password, roles: rolesInput }
|
|
143
|
-
: { identifier: identifier.trim(), password };
|
|
144
|
-
const result = await registerFn(input);
|
|
145
|
-
if (!result.success) {
|
|
146
|
-
fail(response, result.error);
|
|
147
|
-
return;
|
|
148
|
-
}
|
|
149
|
-
ok(response, 201, 'User registered successfully', { user: result.user });
|
|
150
|
-
}
|
|
151
|
-
catch (error) {
|
|
152
|
-
next(error);
|
|
153
|
-
}
|
|
154
|
-
});
|
|
155
|
-
router.post('/login', async (request, response, next) => {
|
|
156
|
-
try {
|
|
157
|
-
const body = parseBody(request.body);
|
|
158
|
-
const { identifier, password } = body;
|
|
159
|
-
if (typeof identifier !== 'string' || identifier.trim().length === 0) {
|
|
160
|
-
throw badRequest('identifier is required and must be a non-empty string');
|
|
161
|
-
}
|
|
162
|
-
if (identifier.length > MAX_IDENTIFIER_LENGTH) {
|
|
163
|
-
throw badRequest(`identifier must not exceed ${MAX_IDENTIFIER_LENGTH} characters`);
|
|
164
|
-
}
|
|
165
|
-
if (typeof password !== 'string' || password.length === 0) {
|
|
166
|
-
throw badRequest('password is required');
|
|
167
|
-
}
|
|
168
|
-
if (password.length > MAX_PASSWORD_LENGTH) {
|
|
169
|
-
throw badRequest(`password must not exceed ${MAX_PASSWORD_LENGTH} characters`);
|
|
170
|
-
}
|
|
171
|
-
const result = await loginFn({ identifier: identifier.trim(), password });
|
|
172
|
-
if (!result.success) {
|
|
173
|
-
fail(response, result.error);
|
|
174
|
-
return;
|
|
175
|
-
}
|
|
176
|
-
setCookie(response, result.refreshToken, config);
|
|
177
|
-
ok(response, 200, 'Login successful', { accessToken: result.accessToken, user: result.user });
|
|
178
|
-
}
|
|
179
|
-
catch (error) {
|
|
180
|
-
next(error);
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
router.post('/refresh', async (request, response, next) => {
|
|
184
|
-
try {
|
|
185
|
-
const fromCookie = readCookie(request.headers['cookie'], getCookieName(config));
|
|
186
|
-
if (!fromCookie) {
|
|
187
|
-
throw new SentriError('UNAUTHORIZED', 'Refresh token cookie is missing');
|
|
188
|
-
}
|
|
189
|
-
const result = await refreshFn(fromCookie);
|
|
190
|
-
if (!result.success) {
|
|
191
|
-
clearCookie(response, config);
|
|
192
|
-
fail(response, result.error);
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
setCookie(response, result.refreshToken, config);
|
|
196
|
-
ok(response, 200, 'Token refreshed', { accessToken: result.accessToken });
|
|
197
|
-
}
|
|
198
|
-
catch (error) {
|
|
199
|
-
next(error);
|
|
200
|
-
}
|
|
201
|
-
});
|
|
202
|
-
router.post('/logout', async (request, response, next) => {
|
|
203
|
-
try {
|
|
204
|
-
const fromCookie = readCookie(request.headers['cookie'], getCookieName(config));
|
|
205
|
-
await logoutFn(fromCookie);
|
|
206
|
-
clearCookie(response, config);
|
|
207
|
-
ok(response, 200, 'Logged out', null);
|
|
208
|
-
}
|
|
209
|
-
catch (error) {
|
|
210
|
-
next(error);
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
router.post('/logout-all', protect(config), async (request, response, next) => {
|
|
214
|
-
try {
|
|
215
|
-
await logoutAllFn(request.user.id);
|
|
216
|
-
clearCookie(response, config);
|
|
217
|
-
ok(response, 200, 'All sessions revoked', null);
|
|
218
|
-
}
|
|
219
|
-
catch (error) {
|
|
220
|
-
next(error);
|
|
221
|
-
}
|
|
222
|
-
});
|
|
223
|
-
router.get('/me', protect(config), (request, response) => {
|
|
224
|
-
ok(response, 200, 'OK', request.user);
|
|
225
|
-
});
|
|
226
|
-
router.post('/users/:userId/roles', protect(config), authorize('admin'), async (request, response, next) => {
|
|
227
|
-
try {
|
|
228
|
-
const body = parseBody(request.body);
|
|
229
|
-
const { roles } = body;
|
|
230
|
-
const rawUserId = request.params['userId'];
|
|
231
|
-
const userId = typeof rawUserId === 'string' ? rawUserId : undefined;
|
|
232
|
-
if (!userId) {
|
|
233
|
-
throw badRequest('userId is required');
|
|
234
|
-
}
|
|
235
|
-
if (!Array.isArray(roles) || roles.length === 0) {
|
|
236
|
-
throw badRequest('roles must be a non-empty array of strings');
|
|
237
|
-
}
|
|
238
|
-
if (!roles.every((role) => typeof role === 'string')) {
|
|
239
|
-
throw badRequest('each role must be a string');
|
|
240
|
-
}
|
|
241
|
-
const result = await assignRolesFn(userId, roles);
|
|
242
|
-
if (!result.success) {
|
|
243
|
-
fail(response, result.error);
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
ok(response, 200, 'Roles assigned successfully', { user: result.user });
|
|
247
|
-
}
|
|
248
|
-
catch (error) {
|
|
249
|
-
next(error);
|
|
250
|
-
}
|
|
251
|
-
});
|
|
252
|
-
// Centralized error handler — converts SentriError (and unexpected errors) to the
|
|
253
|
-
// standard envelope so every endpoint produces a consistent shape on failure.
|
|
254
|
-
router.use((error, _request, response, _next) => {
|
|
255
|
-
if (error instanceof SentriError) {
|
|
256
|
-
fail(response, error);
|
|
257
|
-
}
|
|
258
|
-
else {
|
|
259
|
-
response.status(500).json({ error: true, statusCode: 500, message: 'Internal server error', data: null });
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
return router;
|
|
263
|
-
}
|
|
264
|
-
//# sourceMappingURL=router.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"router.js","sourceRoot":"","sources":["../../src/middleware/router.ts"],"names":[],"mappings":"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"}
|