sentri 1.0.4 → 1.0.6
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 +314 -860
- package/dist/cli.js +79 -26
- package/dist/cli.js.map +1 -1
- package/dist/client.d.ts +34 -69
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +0 -6
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/libs/config.d.ts +45 -1
- package/dist/libs/config.d.ts.map +1 -1
- package/dist/libs/config.js +40 -2
- package/dist/libs/config.js.map +1 -1
- package/dist/libs/hash.d.ts +14 -0
- package/dist/libs/hash.d.ts.map +1 -1
- package/dist/libs/hash.js +14 -0
- package/dist/libs/hash.js.map +1 -1
- package/dist/libs/token.d.ts +37 -0
- package/dist/libs/token.d.ts.map +1 -1
- package/dist/libs/token.js +63 -0
- package/dist/libs/token.js.map +1 -1
- package/dist/middleware/authorize.d.ts +15 -0
- package/dist/middleware/authorize.d.ts.map +1 -1
- package/dist/middleware/authorize.js +18 -3
- package/dist/middleware/authorize.js.map +1 -1
- package/dist/middleware/permit.d.ts +8 -8
- package/dist/middleware/permit.d.ts.map +1 -1
- package/dist/middleware/permit.js +10 -10
- package/dist/middleware/permit.js.map +1 -1
- package/dist/middleware/protect.d.ts +17 -0
- package/dist/middleware/protect.d.ts.map +1 -1
- package/dist/middleware/protect.js +22 -5
- package/dist/middleware/protect.js.map +1 -1
- package/dist/middleware/router.d.ts +10 -6
- package/dist/middleware/router.d.ts.map +1 -1
- package/dist/middleware/router.js +122 -124
- package/dist/middleware/router.js.map +1 -1
- package/dist/services/auth.d.ts +78 -2
- package/dist/services/auth.d.ts.map +1 -1
- package/dist/services/auth.js +90 -5
- package/dist/services/auth.js.map +1 -1
- package/dist/types/auth.d.ts +176 -2
- package/dist/types/auth.d.ts.map +1 -1
- package/dist/types/auth.js +20 -1
- package/dist/types/auth.js.map +1 -1
- package/package.json +11 -3
- package/templates/drizzle/adapter.ts +154 -0
- package/templates/drizzle/auth.ts +47 -0
- package/templates/drizzle/schema.ts +47 -0
- package/templates/prisma/adapter.ts +122 -0
- package/templates/prisma/auth.ts +50 -0
- /package/templates/{schema.prisma → prisma/schema.prisma} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/services/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG/D,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,KAAkB,EAClB,MAAkB;IAElB,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAEvC,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;IACzC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,cAAc,EAAE,kBAAkB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/G,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC1E,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,EAAE,CAAC;IAChG,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;IAExG,MAAM,
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/services/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAC/D,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAG/D;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,KAAkB,EAClB,MAAkB;IAElB,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAEvC,MAAM,cAAc,GAAG,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC;IACzC,MAAM,YAAY,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;IACpF,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,cAAc,EAAE,kBAAkB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/G,CAAC;IAED,MAAM,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IAC3C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,UAAU,CAAC,CAAC;IAC1E,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,EAAE,CAAC;IAChG,CAAC;IAED,MAAM,YAAY,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAC7E,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,UAAU,EAAE,YAAY,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC;IAExG,MAAM,IAAI,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC;IACnE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CACzB,KAAiB,EACjB,MAAkB;IAElB,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAEvC,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,gBAAgB,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IACpF,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,EAAE,CAAC;IAChG,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,cAAc,CAAC,KAAK,CAAC,QAAQ,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IACvE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,qBAAqB,EAAE,qBAAqB,CAAC,EAAE,CAAC;IAChG,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAChF,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;IAEvF,MAAM,IAAI,GAAG,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC;IAChF,MAAM,WAAW,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAClD,MAAM,YAAY,GAAG,gBAAgB,CAAC,OAAO,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAC1D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,YAAY,EAAE,IAAI,EAAE,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,YAAoB,EACpB,MAAkB;IAElB,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAEvC,IAAI,SAAiB,CAAC;IACtB,IAAI,CAAC;QACH,CAAC,EAAE,SAAS,EAAE,GAAG,kBAAkB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,GAAG,YAAY,SAAS;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC;QACpE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,eAAe,EAAE,uBAAuB,CAAC,EAAE,CAAC;IAC5F,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;IACnE,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,cAAc,EAAE,8BAA8B,CAAC,EAAE,CAAC;IAClG,CAAC;IAED,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,EAAE,CAAC;QACnC,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACjD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,eAAe,EAAE,qBAAqB,CAAC,EAAE,CAAC;IAC1F,CAAC;IAED,6CAA6C;IAC7C,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAChF,MAAM,UAAU,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC,CAAC;IAEhG,MAAM,IAAI,GAAG,EAAE,EAAE,EAAE,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,UAAU,EAAE,OAAO,CAAC,IAAI,CAAC,UAAU,EAAE,KAAK,EAAE,OAAO,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACrG,MAAM,cAAc,GAAG,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACrD,MAAM,eAAe,GAAG,gBAAgB,CAAC,UAAU,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;IAChE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,EAAE,YAAY,EAAE,eAAe,EAAE,IAAI,EAAE,CAAC;AAC7F,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,MAAM,CAC1B,YAAoB,EACpB,MAAkB;IAElB,IAAI,SAAiB,CAAC;IACtB,IAAI,CAAC;QACH,CAAC,EAAE,SAAS,EAAE,GAAG,kBAAkB,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAC7D,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,qCAAqC;IAC/C,CAAC;IACD,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;AAChE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,MAAc,EACd,MAAkB;IAElB,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,CAAC,MAAM,CAAC,CAAC;AACvE,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,MAAc,EACd,UAAoB,EACpB,MAAkB;IAElB,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,CAAC;IAEvC,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;IACtF,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,cAAc,EAAE,kBAAkB,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;IAC/G,CAAC;IAED,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3D,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,SAAS,CAAC,gBAAgB,EAAE,gBAAgB,CAAC,EAAE,CAAC;IACtF,CAAC;IAED,MAAM,WAAW,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,CAAC,GAAG,KAAK,CAAC,KAAK,EAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IACzE,MAAM,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAE7D,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,KAAK,CAAC,EAAE,EAAE,UAAU,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,EAAE,WAAW,EAAE,EAAE,CAAC;AACrG,CAAC"}
|
package/dist/types/auth.d.ts
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import type { AuthError } from '../errors/AuthError.js';
|
|
1
|
+
import type { AuthError, AuthErrorCode } from '../errors/AuthError.js';
|
|
2
2
|
export type { AuthError };
|
|
3
|
+
/** Standard API response envelope returned by all built-in router endpoints. */
|
|
4
|
+
export interface ApiResponse<T = null> {
|
|
5
|
+
error: boolean;
|
|
6
|
+
statusCode: number;
|
|
7
|
+
message: string;
|
|
8
|
+
data: T | null;
|
|
9
|
+
}
|
|
10
|
+
/** Maps an {@link AuthErrorCode} to its corresponding HTTP status code. */
|
|
11
|
+
export declare function authErrorStatus(code: AuthErrorCode): number;
|
|
3
12
|
/** Shape of a user row returned by the adapter — used internally by the library. */
|
|
4
13
|
export interface UserRecord {
|
|
5
14
|
id: string;
|
|
@@ -58,6 +67,11 @@ export interface AuthAdapter {
|
|
|
58
67
|
create(data: CreateUserData): Promise<{
|
|
59
68
|
id: string;
|
|
60
69
|
}>;
|
|
70
|
+
/**
|
|
71
|
+
* Replace the complete role list for a user.
|
|
72
|
+
* Called by `assignRoles` after merging the new roles with the existing ones.
|
|
73
|
+
*/
|
|
74
|
+
updateRoles(userId: string, roles: string[]): Promise<void>;
|
|
61
75
|
};
|
|
62
76
|
session: {
|
|
63
77
|
/**
|
|
@@ -83,6 +97,132 @@ export interface AuthAdapter {
|
|
|
83
97
|
deleteAllForUser(userId: string): Promise<void>;
|
|
84
98
|
};
|
|
85
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Custom service functions for the built-in auth router.
|
|
102
|
+
*
|
|
103
|
+
* Each key matches the internal service function name. When provided, the
|
|
104
|
+
* custom function replaces the default service call for that route while the
|
|
105
|
+
* router still handles request parsing, input validation, and response formatting.
|
|
106
|
+
*
|
|
107
|
+
* The function signatures mirror the internal services exactly but without the
|
|
108
|
+
* `config` parameter — the library passes config at bind time.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* createAuth({
|
|
112
|
+
* // ...
|
|
113
|
+
* router: {
|
|
114
|
+
* login: async (input) => {
|
|
115
|
+
* // add OTP check, custom user lookup, etc.
|
|
116
|
+
* // must return AuthResult
|
|
117
|
+
* },
|
|
118
|
+
* signup: async (input) => {
|
|
119
|
+
* // send welcome email, set default profile, etc.
|
|
120
|
+
* // must return SignupResult
|
|
121
|
+
* },
|
|
122
|
+
* },
|
|
123
|
+
* });
|
|
124
|
+
*/
|
|
125
|
+
export interface RouterHandlers {
|
|
126
|
+
/**
|
|
127
|
+
* Replaces the default signup service.
|
|
128
|
+
*
|
|
129
|
+
* The router validates the request body (identifier, password, roles) first,
|
|
130
|
+
* then calls this function with the parsed input. Must return a `SignupResult`.
|
|
131
|
+
* If omitted, the library's built-in signup logic runs instead.
|
|
132
|
+
*
|
|
133
|
+
* @example
|
|
134
|
+
* signup: async (input) => {
|
|
135
|
+
* const result = await defaultSignup(input);
|
|
136
|
+
* if (result.success) {
|
|
137
|
+
* await emailService.sendWelcome(input.identifier);
|
|
138
|
+
* }
|
|
139
|
+
* return result;
|
|
140
|
+
* }
|
|
141
|
+
*/
|
|
142
|
+
signup?: (input: SignupInput) => Promise<SignupResult>;
|
|
143
|
+
/**
|
|
144
|
+
* Replaces the default login service.
|
|
145
|
+
*
|
|
146
|
+
* The router validates the request body (identifier, password) first,
|
|
147
|
+
* then calls this function with the parsed input. Must return an `AuthResult`.
|
|
148
|
+
* If omitted, the library's built-in login logic runs instead.
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* login: async (input) => {
|
|
152
|
+
* // verify OTP before issuing tokens
|
|
153
|
+
* const otpValid = await redis.get(`otp:${input.identifier}`);
|
|
154
|
+
* if (!otpValid) {
|
|
155
|
+
* return { success: false, error: new AuthError('INVALID_CREDENTIALS', 'OTP required') };
|
|
156
|
+
* }
|
|
157
|
+
* return defaultLogin(input);
|
|
158
|
+
* }
|
|
159
|
+
*/
|
|
160
|
+
login?: (input: LoginInput) => Promise<AuthResult>;
|
|
161
|
+
/**
|
|
162
|
+
* Replaces the default refresh service.
|
|
163
|
+
*
|
|
164
|
+
* Receives the raw refresh token string extracted from the cookie.
|
|
165
|
+
* Must return a `RefreshResult`. If omitted, the built-in session-rotation
|
|
166
|
+
* logic runs instead.
|
|
167
|
+
*
|
|
168
|
+
* @example
|
|
169
|
+
* refresh: async (refreshToken) => {
|
|
170
|
+
* const result = await defaultRefresh(refreshToken);
|
|
171
|
+
* if (result.success) {
|
|
172
|
+
* await auditLog.record('token_rotated', result.user.id);
|
|
173
|
+
* }
|
|
174
|
+
* return result;
|
|
175
|
+
* }
|
|
176
|
+
*/
|
|
177
|
+
refresh?: (refreshToken: string) => Promise<RefreshResult>;
|
|
178
|
+
/**
|
|
179
|
+
* Replaces the default logout service.
|
|
180
|
+
*
|
|
181
|
+
* Receives the raw refresh token from the cookie, or `undefined` if no cookie
|
|
182
|
+
* was present. The router clears the cookie after this function resolves.
|
|
183
|
+
* If omitted, the built-in session deletion logic runs instead.
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* logout: async (refreshToken) => {
|
|
187
|
+
* if (refreshToken) {
|
|
188
|
+
* await defaultLogout(refreshToken);
|
|
189
|
+
* await auditLog.record('logout', refreshToken);
|
|
190
|
+
* }
|
|
191
|
+
* }
|
|
192
|
+
*/
|
|
193
|
+
logout?: (refreshToken: string | undefined) => Promise<void>;
|
|
194
|
+
/**
|
|
195
|
+
* Replaces the default logoutAll service.
|
|
196
|
+
*
|
|
197
|
+
* Receives the authenticated user's ID (from `req.user`, set by `protect()`).
|
|
198
|
+
* If omitted, the built-in "delete all sessions" logic runs instead.
|
|
199
|
+
*
|
|
200
|
+
* @example
|
|
201
|
+
* logoutAll: async (userId) => {
|
|
202
|
+
* await defaultLogoutAll(userId);
|
|
203
|
+
* await notifyService.push(userId, 'You have been signed out from all devices.');
|
|
204
|
+
* }
|
|
205
|
+
*/
|
|
206
|
+
logoutAll?: (userId: string) => Promise<void>;
|
|
207
|
+
/**
|
|
208
|
+
* Replaces the default assignRoles service.
|
|
209
|
+
*
|
|
210
|
+
* The router validates the request body and params first, then calls this
|
|
211
|
+
* function with the target `userId` and the validated `roles` array.
|
|
212
|
+
* Must return an `AssignRolesResult`. If omitted, the built-in role-merge
|
|
213
|
+
* logic runs instead.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* assignRoles: async (userId, roles) => {
|
|
217
|
+
* const result = await defaultAssignRoles(userId, roles);
|
|
218
|
+
* if (result.success) {
|
|
219
|
+
* await auditLog.record('roles_assigned', { userId, roles });
|
|
220
|
+
* }
|
|
221
|
+
* return result;
|
|
222
|
+
* }
|
|
223
|
+
*/
|
|
224
|
+
assignRoles?: (userId: string, roles: string[]) => Promise<AssignRolesResult>;
|
|
225
|
+
}
|
|
86
226
|
/**
|
|
87
227
|
* Configuration passed to {@link createAuth}.
|
|
88
228
|
*
|
|
@@ -132,6 +272,24 @@ export interface AuthConfig<TRole extends string = string> {
|
|
|
132
272
|
validRoles: readonly TRole[];
|
|
133
273
|
/** ORM adapter that connects the library to your database. */
|
|
134
274
|
adapter: AuthAdapter;
|
|
275
|
+
/**
|
|
276
|
+
* Custom service functions for individual routes in the built-in auth router.
|
|
277
|
+
*
|
|
278
|
+
* The router still handles request parsing, validation, and response formatting.
|
|
279
|
+
* Only the core service logic is replaced by your function.
|
|
280
|
+
*
|
|
281
|
+
* @example
|
|
282
|
+
* createAuth({
|
|
283
|
+
* // ...
|
|
284
|
+
* router: {
|
|
285
|
+
* login: async (input) => {
|
|
286
|
+
* // verify OTP, then delegate to default or return custom result
|
|
287
|
+
* return { success: true, accessToken, refreshToken, user };
|
|
288
|
+
* },
|
|
289
|
+
* },
|
|
290
|
+
* });
|
|
291
|
+
*/
|
|
292
|
+
router?: RouterHandlers;
|
|
135
293
|
/**
|
|
136
294
|
* When set, the built-in router (`auth.router()`) stores the refresh token
|
|
137
295
|
* in an httpOnly cookie instead of returning it in the response body.
|
|
@@ -191,7 +349,15 @@ export interface AuthUser<TRole extends string = string> {
|
|
|
191
349
|
identifier: string;
|
|
192
350
|
roles: TRole[];
|
|
193
351
|
}
|
|
194
|
-
/** Return type of `signup
|
|
352
|
+
/** Return type of `signup`. */
|
|
353
|
+
export type SignupResult<TRole extends string = string> = {
|
|
354
|
+
success: true;
|
|
355
|
+
user: AuthUser<TRole>;
|
|
356
|
+
} | {
|
|
357
|
+
success: false;
|
|
358
|
+
error: AuthError;
|
|
359
|
+
};
|
|
360
|
+
/** Return type of `login`. */
|
|
195
361
|
export type AuthResult<TRole extends string = string> = {
|
|
196
362
|
success: true;
|
|
197
363
|
accessToken: string;
|
|
@@ -201,6 +367,14 @@ export type AuthResult<TRole extends string = string> = {
|
|
|
201
367
|
success: false;
|
|
202
368
|
error: AuthError;
|
|
203
369
|
};
|
|
370
|
+
/** Return type of `assignRoles`. */
|
|
371
|
+
export type AssignRolesResult<TRole extends string = string> = {
|
|
372
|
+
success: true;
|
|
373
|
+
user: AuthUser<TRole>;
|
|
374
|
+
} | {
|
|
375
|
+
success: false;
|
|
376
|
+
error: AuthError;
|
|
377
|
+
};
|
|
204
378
|
/** Return type of `refresh`. */
|
|
205
379
|
export type RefreshResult<TRole extends string = string> = {
|
|
206
380
|
success: true;
|
package/dist/types/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/types/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/types/auth.ts"],"names":[],"mappings":"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"}
|
package/dist/types/auth.js
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
/** Maps an {@link AuthErrorCode} to its corresponding HTTP status code. */
|
|
2
|
+
export function authErrorStatus(code) {
|
|
3
|
+
switch (code) {
|
|
4
|
+
case 'UNAUTHORIZED':
|
|
5
|
+
case 'INVALID_CREDENTIALS':
|
|
6
|
+
case 'TOKEN_EXPIRED':
|
|
7
|
+
case 'TOKEN_INVALID':
|
|
8
|
+
return 401;
|
|
9
|
+
case 'FORBIDDEN':
|
|
10
|
+
return 403;
|
|
11
|
+
case 'USER_NOT_FOUND':
|
|
12
|
+
return 404;
|
|
13
|
+
case 'USER_ALREADY_EXISTS':
|
|
14
|
+
return 409;
|
|
15
|
+
case 'CONFIGURATION_ERROR':
|
|
16
|
+
return 500;
|
|
17
|
+
default:
|
|
18
|
+
return 400;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
2
21
|
//# sourceMappingURL=auth.js.map
|
package/dist/types/auth.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/types/auth.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/types/auth.ts"],"names":[],"mappings":"AAYA,2EAA2E;AAC3E,MAAM,UAAU,eAAe,CAAC,IAAmB;IACjD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,cAAc,CAAC;QACpB,KAAK,qBAAqB,CAAC;QAC3B,KAAK,eAAe,CAAC;QACrB,KAAK,eAAe;YAClB,OAAO,GAAG,CAAC;QACb,KAAK,WAAW;YACd,OAAO,GAAG,CAAC;QACb,KAAK,gBAAgB;YACnB,OAAO,GAAG,CAAC;QACb,KAAK,qBAAqB;YACxB,OAAO,GAAG,CAAC;QACb,KAAK,qBAAqB;YACxB,OAAO,GAAG,CAAC;QACb;YACE,OAAO,GAAG,CAAC;IACf,CAAC;AACH,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sentri",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Personal auth/authorization library for Express + Postgres",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -20,7 +20,10 @@
|
|
|
20
20
|
"templates"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
23
|
-
"build": "tsc"
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"test": "vitest run",
|
|
25
|
+
"test:watch": "vitest",
|
|
26
|
+
"test:coverage": "vitest run --coverage"
|
|
24
27
|
},
|
|
25
28
|
"keywords": [
|
|
26
29
|
"auth",
|
|
@@ -37,8 +40,13 @@
|
|
|
37
40
|
"@types/express": "^5.0.6",
|
|
38
41
|
"@types/jsonwebtoken": "^9.0.10",
|
|
39
42
|
"@types/node": "^22.20.0",
|
|
43
|
+
"@types/supertest": "^7.2.0",
|
|
44
|
+
"@vitest/coverage-v8": "^4.1.9",
|
|
45
|
+
"express": "^5.2.1",
|
|
46
|
+
"supertest": "^7.2.2",
|
|
40
47
|
"tsx": "^4.22.4",
|
|
41
|
-
"typescript": "^6.0.3"
|
|
48
|
+
"typescript": "^6.0.3",
|
|
49
|
+
"vitest": "^4.1.9"
|
|
42
50
|
},
|
|
43
51
|
"dependencies": {
|
|
44
52
|
"bcrypt": "^6.0.0",
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
// sentri — Drizzle adapter template
|
|
2
|
+
// Generated by: npx sentri generate drizzle
|
|
3
|
+
//
|
|
4
|
+
// Adjust the import paths to match your project structure.
|
|
5
|
+
// This template assumes tables: users, roles, userRoles, sessions.
|
|
6
|
+
// Column names follow the sentri Drizzle schema convention.
|
|
7
|
+
//
|
|
8
|
+
// Replace NodePgDatabase with the type that matches your Drizzle driver:
|
|
9
|
+
// import { type NodePgDatabase } from 'drizzle-orm/node-postgres'; (default)
|
|
10
|
+
// import { type LibSQLDatabase } from 'drizzle-orm/libsql';
|
|
11
|
+
// import { type MySql2Database } from 'drizzle-orm/mysql2';
|
|
12
|
+
|
|
13
|
+
import { eq } from 'drizzle-orm';
|
|
14
|
+
import type { NodePgDatabase } from 'drizzle-orm/node-postgres';
|
|
15
|
+
import { AuthError, type AuthAdapter } from 'sentri';
|
|
16
|
+
import { users, roles, userRoles, sessions } from './schema.js';
|
|
17
|
+
|
|
18
|
+
export function createAdapter(db: NodePgDatabase): AuthAdapter {
|
|
19
|
+
if (!db) {
|
|
20
|
+
throw new AuthError(
|
|
21
|
+
'CONFIGURATION_ERROR',
|
|
22
|
+
'createAdapter requires a Drizzle db instance. Did you forget to pass it?\n' +
|
|
23
|
+
'Example: createAdapter(db)',
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
user: {
|
|
28
|
+
async findByIdentifier(identifier) {
|
|
29
|
+
const rows = await db
|
|
30
|
+
.select({
|
|
31
|
+
id: users.id,
|
|
32
|
+
identifier: users.identifier,
|
|
33
|
+
passwordHash: users.passwordHash,
|
|
34
|
+
roleName: roles.name,
|
|
35
|
+
})
|
|
36
|
+
.from(users)
|
|
37
|
+
.leftJoin(userRoles, eq(users.id, userRoles.userId))
|
|
38
|
+
.leftJoin(roles, eq(userRoles.roleId, roles.id))
|
|
39
|
+
.where(eq(users.identifier, identifier));
|
|
40
|
+
|
|
41
|
+
if (rows.length === 0) return null;
|
|
42
|
+
return {
|
|
43
|
+
id: rows[0]!.id,
|
|
44
|
+
identifier: rows[0]!.identifier,
|
|
45
|
+
passwordHash: rows[0]!.passwordHash,
|
|
46
|
+
roles: rows.flatMap((row) => (row.roleName ? [row.roleName] : [])),
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
async findById(id) {
|
|
51
|
+
const rows = await db
|
|
52
|
+
.select({
|
|
53
|
+
id: users.id,
|
|
54
|
+
identifier: users.identifier,
|
|
55
|
+
passwordHash: users.passwordHash,
|
|
56
|
+
roleName: roles.name,
|
|
57
|
+
})
|
|
58
|
+
.from(users)
|
|
59
|
+
.leftJoin(userRoles, eq(users.id, userRoles.userId))
|
|
60
|
+
.leftJoin(roles, eq(userRoles.roleId, roles.id))
|
|
61
|
+
.where(eq(users.id, id));
|
|
62
|
+
|
|
63
|
+
if (rows.length === 0) return null;
|
|
64
|
+
return {
|
|
65
|
+
id: rows[0]!.id,
|
|
66
|
+
identifier: rows[0]!.identifier,
|
|
67
|
+
passwordHash: rows[0]!.passwordHash,
|
|
68
|
+
roles: rows.flatMap((row) => (row.roleName ? [row.roleName] : [])),
|
|
69
|
+
};
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async create({ identifier, passwordHash, roles: roleNames }) {
|
|
73
|
+
const [user] = await db
|
|
74
|
+
.insert(users)
|
|
75
|
+
.values({ identifier, passwordHash })
|
|
76
|
+
.returning({ id: users.id });
|
|
77
|
+
|
|
78
|
+
if (roleNames.length > 0) {
|
|
79
|
+
await Promise.all(
|
|
80
|
+
roleNames.map(async (name) => {
|
|
81
|
+
const existing = await db.select().from(roles).where(eq(roles.name, name));
|
|
82
|
+
const role = existing[0] ?? (await db.insert(roles).values({ name }).returning())[0];
|
|
83
|
+
await db.insert(userRoles).values({ userId: user!.id, roleId: role!.id });
|
|
84
|
+
}),
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
return { id: user!.id };
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
async updateRoles(userId, roleNames) {
|
|
91
|
+
await db.delete(userRoles).where(eq(userRoles.userId, userId));
|
|
92
|
+
await Promise.all(
|
|
93
|
+
roleNames.map(async (name) => {
|
|
94
|
+
const existing = await db.select().from(roles).where(eq(roles.name, name));
|
|
95
|
+
const role = existing[0] ?? (await db.insert(roles).values({ name }).returning())[0];
|
|
96
|
+
await db.insert(userRoles).values({ userId, roleId: role!.id });
|
|
97
|
+
}),
|
|
98
|
+
);
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
session: {
|
|
103
|
+
async create({ userId, expiresAt }) {
|
|
104
|
+
const [session] = await db
|
|
105
|
+
.insert(sessions)
|
|
106
|
+
.values({ userId, expiresAt })
|
|
107
|
+
.returning({ id: sessions.id });
|
|
108
|
+
return { id: session!.id };
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
async findById(sessionId) {
|
|
112
|
+
const rows = await db
|
|
113
|
+
.select({
|
|
114
|
+
sessionId: sessions.id,
|
|
115
|
+
sessionUserId: sessions.userId,
|
|
116
|
+
sessionExpiresAt: sessions.expiresAt,
|
|
117
|
+
sessionCreatedAt: sessions.createdAt,
|
|
118
|
+
userId: users.id,
|
|
119
|
+
identifier: users.identifier,
|
|
120
|
+
passwordHash: users.passwordHash,
|
|
121
|
+
roleName: roles.name,
|
|
122
|
+
})
|
|
123
|
+
.from(sessions)
|
|
124
|
+
.innerJoin(users, eq(sessions.userId, users.id))
|
|
125
|
+
.leftJoin(userRoles, eq(users.id, userRoles.userId))
|
|
126
|
+
.leftJoin(roles, eq(userRoles.roleId, roles.id))
|
|
127
|
+
.where(eq(sessions.id, sessionId));
|
|
128
|
+
|
|
129
|
+
if (rows.length === 0) return null;
|
|
130
|
+
const first = rows[0]!;
|
|
131
|
+
return {
|
|
132
|
+
id: first.sessionId,
|
|
133
|
+
userId: first.sessionUserId,
|
|
134
|
+
expiresAt: first.sessionExpiresAt,
|
|
135
|
+
createdAt: first.sessionCreatedAt,
|
|
136
|
+
user: {
|
|
137
|
+
id: first.userId,
|
|
138
|
+
identifier: first.identifier,
|
|
139
|
+
passwordHash: first.passwordHash,
|
|
140
|
+
roles: rows.flatMap((row) => (row.roleName ? [row.roleName] : [])),
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
|
|
145
|
+
async delete(sessionId) {
|
|
146
|
+
await db.delete(sessions).where(eq(sessions.id, sessionId));
|
|
147
|
+
},
|
|
148
|
+
|
|
149
|
+
async deleteAllForUser(userId) {
|
|
150
|
+
await db.delete(sessions).where(eq(sessions.userId, userId));
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// sentri — auth config template (Drizzle)
|
|
2
|
+
// Generated by: npx sentri generate drizzle
|
|
3
|
+
//
|
|
4
|
+
// Update validRoles to match the roles your application uses.
|
|
5
|
+
// Add JWT_SECRET to your .env file.
|
|
6
|
+
// If you already have a db instance defined elsewhere, import it instead.
|
|
7
|
+
|
|
8
|
+
import { createAuth } from 'sentri';
|
|
9
|
+
import { db } from '../db.js'; // adjust path to your db instance
|
|
10
|
+
import { createAdapter } from './adapter.js';
|
|
11
|
+
|
|
12
|
+
export const auth = createAuth({
|
|
13
|
+
secret: process.env.JWT_SECRET!,
|
|
14
|
+
validRoles: ['user', 'admin'] as const,
|
|
15
|
+
adapter: createAdapter(db),
|
|
16
|
+
// accessExpiresIn: '15m',
|
|
17
|
+
// refreshExpiresIn: '7d',
|
|
18
|
+
// algorithm: 'HS256',
|
|
19
|
+
// saltRounds: 12,
|
|
20
|
+
cookie: {
|
|
21
|
+
secure: process.env.NODE_ENV === 'production',
|
|
22
|
+
// name: 'refresh_token',
|
|
23
|
+
// httpOnly: true,
|
|
24
|
+
// sameSite: 'strict',
|
|
25
|
+
// path: '/',
|
|
26
|
+
},
|
|
27
|
+
// router: {
|
|
28
|
+
// signup: async (input) => {
|
|
29
|
+
// // custom signup logic — must return SignupResult
|
|
30
|
+
// },
|
|
31
|
+
// login: async (input) => {
|
|
32
|
+
// // custom login logic — must return AuthResult
|
|
33
|
+
// },
|
|
34
|
+
// refresh: async (refreshToken) => {
|
|
35
|
+
// // custom refresh logic — must return RefreshResult
|
|
36
|
+
// },
|
|
37
|
+
// logout: async (refreshToken) => {
|
|
38
|
+
// // custom logout logic
|
|
39
|
+
// },
|
|
40
|
+
// logoutAll: async (userId) => {
|
|
41
|
+
// // custom logout-all logic
|
|
42
|
+
// },
|
|
43
|
+
// assignRoles: async (userId, roles) => {
|
|
44
|
+
// // custom assignRoles logic — must return AssignRolesResult
|
|
45
|
+
// },
|
|
46
|
+
// },
|
|
47
|
+
});
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// sentri — Drizzle schema template (PostgreSQL / node-postgres)
|
|
2
|
+
// Generated by: npx sentri generate drizzle
|
|
3
|
+
//
|
|
4
|
+
// Field mapping: library uses `identifier` internally, but your DB column
|
|
5
|
+
// can be named anything — adjust the first argument of text() on the
|
|
6
|
+
// identifier column to match your existing schema, or leave it as-is.
|
|
7
|
+
//
|
|
8
|
+
// text('email') — login via email
|
|
9
|
+
// text('username') — login via username
|
|
10
|
+
// text('phone') — login via phone number
|
|
11
|
+
|
|
12
|
+
import { pgTable, primaryKey, text, timestamp } from 'drizzle-orm/pg-core';
|
|
13
|
+
|
|
14
|
+
export const users = pgTable('users', {
|
|
15
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
16
|
+
identifier: text('email').notNull().unique(),
|
|
17
|
+
passwordHash: text('password_hash').notNull(),
|
|
18
|
+
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
19
|
+
updatedAt: timestamp('updated_at').notNull().$onUpdateFn(() => new Date()),
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
export const roles = pgTable('roles', {
|
|
23
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
24
|
+
name: text('name').notNull().unique(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export const userRoles = pgTable(
|
|
28
|
+
'user_roles',
|
|
29
|
+
{
|
|
30
|
+
userId: text('user_id')
|
|
31
|
+
.notNull()
|
|
32
|
+
.references(() => users.id, { onDelete: 'cascade' }),
|
|
33
|
+
roleId: text('role_id')
|
|
34
|
+
.notNull()
|
|
35
|
+
.references(() => roles.id, { onDelete: 'cascade' }),
|
|
36
|
+
},
|
|
37
|
+
(table) => [primaryKey({ columns: [table.userId, table.roleId] })],
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
export const sessions = pgTable('sessions', {
|
|
41
|
+
id: text('id').primaryKey().$defaultFn(() => crypto.randomUUID()),
|
|
42
|
+
userId: text('user_id')
|
|
43
|
+
.notNull()
|
|
44
|
+
.references(() => users.id, { onDelete: 'cascade' }),
|
|
45
|
+
expiresAt: timestamp('expires_at').notNull(),
|
|
46
|
+
createdAt: timestamp('created_at').notNull().defaultNow(),
|
|
47
|
+
});
|