@shware/security 7.1.1 → 8.0.1
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 +113 -32
- package/dist/auth/attributes.cjs +4 -3
- package/dist/auth/attributes.cjs.map +1 -1
- package/dist/auth/attributes.d.cts +3 -3
- package/dist/auth/attributes.d.cts.map +1 -1
- package/dist/auth/attributes.d.mts +3 -3
- package/dist/auth/attributes.d.mts.map +1 -1
- package/dist/auth/attributes.mjs +4 -3
- package/dist/auth/attributes.mjs.map +1 -1
- package/dist/auth/desktop-codes.cjs +57 -0
- package/dist/auth/desktop-codes.cjs.map +1 -0
- package/dist/auth/desktop-codes.d.cts +24 -0
- package/dist/auth/desktop-codes.d.cts.map +1 -0
- package/dist/auth/desktop-codes.d.mts +24 -0
- package/dist/auth/desktop-codes.d.mts.map +1 -0
- package/dist/auth/desktop-codes.mjs +55 -0
- package/dist/auth/desktop-codes.mjs.map +1 -0
- package/dist/auth/email-codes.cjs +77 -0
- package/dist/auth/email-codes.cjs.map +1 -0
- package/dist/auth/email-codes.d.cts +39 -0
- package/dist/auth/email-codes.d.cts.map +1 -0
- package/dist/auth/email-codes.d.mts +39 -0
- package/dist/auth/email-codes.d.mts.map +1 -0
- package/dist/auth/email-codes.mjs +75 -0
- package/dist/auth/email-codes.mjs.map +1 -0
- package/dist/auth/error.cjs +47 -0
- package/dist/auth/error.cjs.map +1 -0
- package/dist/auth/error.d.cts +88 -0
- package/dist/auth/error.d.cts.map +1 -0
- package/dist/auth/error.d.mts +88 -0
- package/dist/auth/error.d.mts.map +1 -0
- package/dist/auth/error.mjs +45 -0
- package/dist/auth/error.mjs.map +1 -0
- package/dist/auth/index.cjs +249 -384
- package/dist/auth/index.cjs.map +1 -1
- package/dist/auth/index.d.cts +66 -55
- package/dist/auth/index.d.cts.map +1 -1
- package/dist/auth/index.d.mts +66 -55
- package/dist/auth/index.d.mts.map +1 -1
- package/dist/auth/index.mjs +252 -387
- package/dist/auth/index.mjs.map +1 -1
- package/dist/auth/respond.cjs +99 -0
- package/dist/auth/respond.cjs.map +1 -0
- package/dist/auth/respond.d.cts +39 -0
- package/dist/auth/respond.d.cts.map +1 -0
- package/dist/auth/respond.d.mts +39 -0
- package/dist/auth/respond.d.mts.map +1 -0
- package/dist/auth/respond.mjs +92 -0
- package/dist/auth/respond.mjs.map +1 -0
- package/dist/auth/types.d.cts +27 -11
- package/dist/auth/types.d.cts.map +1 -1
- package/dist/auth/types.d.mts +27 -11
- package/dist/auth/types.d.mts.map +1 -1
- package/dist/index.cjs +5 -0
- package/dist/index.d.cts +4 -1
- package/dist/index.d.mts +4 -1
- package/dist/index.mjs +3 -1
- package/dist/oauth2/error.cjs +0 -7
- package/dist/oauth2/error.cjs.map +1 -1
- package/dist/oauth2/error.d.cts +0 -3
- package/dist/oauth2/error.d.cts.map +1 -1
- package/dist/oauth2/error.d.mts +0 -3
- package/dist/oauth2/error.d.mts.map +1 -1
- package/dist/oauth2/error.mjs +1 -7
- package/dist/oauth2/error.mjs.map +1 -1
- package/dist/oauth2/provider/common.cjs +17 -3
- package/dist/oauth2/provider/common.cjs.map +1 -1
- package/dist/oauth2/provider/common.d.cts.map +1 -1
- package/dist/oauth2/provider/common.d.mts.map +1 -1
- package/dist/oauth2/provider/common.mjs +17 -3
- package/dist/oauth2/provider/common.mjs.map +1 -1
- package/dist/oauth2/provider/facebook.cjs +2 -4
- package/dist/oauth2/provider/facebook.cjs.map +1 -1
- package/dist/oauth2/provider/facebook.d.cts +0 -1
- package/dist/oauth2/provider/facebook.d.cts.map +1 -1
- package/dist/oauth2/provider/facebook.d.mts +0 -1
- package/dist/oauth2/provider/facebook.d.mts.map +1 -1
- package/dist/oauth2/provider/facebook.mjs +2 -4
- package/dist/oauth2/provider/facebook.mjs.map +1 -1
- package/dist/oauth2/provider/github.cjs +1 -1
- package/dist/oauth2/provider/github.cjs.map +1 -1
- package/dist/oauth2/provider/github.d.cts +1 -1
- package/dist/oauth2/provider/github.d.cts.map +1 -1
- package/dist/oauth2/provider/github.d.mts +1 -1
- package/dist/oauth2/provider/github.d.mts.map +1 -1
- package/dist/oauth2/provider/github.mjs +1 -1
- package/dist/oauth2/provider/github.mjs.map +1 -1
- package/dist/oauth2/provider/x.cjs +6 -4
- package/dist/oauth2/provider/x.cjs.map +1 -1
- package/dist/oauth2/provider/x.d.cts +3 -2
- package/dist/oauth2/provider/x.d.cts.map +1 -1
- package/dist/oauth2/provider/x.d.mts +3 -2
- package/dist/oauth2/provider/x.d.mts.map +1 -1
- package/dist/oauth2/provider/x.mjs +6 -4
- package/dist/oauth2/provider/x.mjs.map +1 -1
- package/dist/oauth2/types.cjs.map +1 -1
- package/dist/oauth2/types.d.cts +5 -0
- package/dist/oauth2/types.d.cts.map +1 -1
- package/dist/oauth2/types.d.mts +5 -0
- package/dist/oauth2/types.d.mts.map +1 -1
- package/dist/oauth2/types.mjs.map +1 -1
- package/dist/session/common.cjs +10 -13
- package/dist/session/common.cjs.map +1 -1
- package/dist/session/common.d.cts +7 -3
- package/dist/session/common.d.cts.map +1 -1
- package/dist/session/common.d.mts +7 -3
- package/dist/session/common.d.mts.map +1 -1
- package/dist/session/common.mjs +10 -12
- package/dist/session/common.mjs.map +1 -1
- package/dist/session/db.cjs +2 -2
- package/dist/session/db.cjs.map +1 -1
- package/dist/session/db.mjs +3 -3
- package/dist/session/db.mjs.map +1 -1
- package/dist/session/redis.cjs +6 -8
- package/dist/session/redis.cjs.map +1 -1
- package/dist/session/redis.d.cts.map +1 -1
- package/dist/session/redis.d.mts.map +1 -1
- package/dist/session/redis.mjs +7 -9
- package/dist/session/redis.mjs.map +1 -1
- package/dist/session/types.d.cts +0 -12
- package/dist/session/types.d.cts.map +1 -1
- package/dist/session/types.d.mts +0 -12
- package/dist/session/types.d.mts.map +1 -1
- package/dist/utils/valid.cjs +34 -52
- package/dist/utils/valid.cjs.map +1 -1
- package/dist/utils/valid.d.cts +8 -23
- package/dist/utils/valid.d.cts.map +1 -1
- package/dist/utils/valid.d.mts +8 -23
- package/dist/utils/valid.d.mts.map +1 -1
- package/dist/utils/valid.mjs +32 -50
- package/dist/utils/valid.mjs.map +1 -1
- package/package.json +3 -3
package/dist/auth/index.d.mts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Principal } from "../core/index.mjs";
|
|
2
2
|
import { Session } from "../session/types.mjs";
|
|
3
|
+
import { EmailSender } from "./email-codes.mjs";
|
|
3
4
|
import { AuthConfig, AuthService, AuthorizedHandler, LoggedHandler, OAuth2AuthorizationOptions } from "./types.mjs";
|
|
4
5
|
export declare const PATH: {
|
|
5
6
|
readonly CSRF: '/csrf';
|
|
@@ -12,13 +13,18 @@ export declare const PATH: {
|
|
|
12
13
|
readonly LOGIN_OAUTH2_NATIVE: '/login/oauth2/native/:provider';
|
|
13
14
|
readonly LOGIN_OAUTH2_ONETAP: '/login/oauth2/onetap/google';
|
|
14
15
|
readonly LOGIN_EMAIL: '/login/email';
|
|
15
|
-
readonly LOGIN_PHONE: '/login/phone';
|
|
16
16
|
readonly SEND_EMAIL_VERIFICATION_CODE: '/auth/sendEmailVerificationCode';
|
|
17
|
-
readonly SEND_PHONE_VERIFICATION_CODE: '/auth/sendPhoneVerificationCode';
|
|
18
17
|
readonly DESKTOP_AUTHORIZE: '/auth/desktop/authorize';
|
|
19
18
|
readonly DESKTOP_EXCHANGE: '/auth/desktop/exchange';
|
|
20
19
|
readonly CLEANUP_EXPIRED_SESSIONS: '/sessions/expired/cleanup';
|
|
21
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* The login routes and session helpers. Every route is one straight line: read and validate the
|
|
23
|
+
* request, check the credential, call the host's `onAuthorized`, sign in. Failures are thrown and
|
|
24
|
+
* answered in one place per kind of route: a JSON route runs under `guard`, which maps a
|
|
25
|
+
* `RequestError`, an `AuthenticationError` or an `OAuth2Error` to its google.rpc body (see
|
|
26
|
+
* `respond.ts`); the redirect callback maps the same errors to `errorUri` parameters (`redirect`).
|
|
27
|
+
*/
|
|
22
28
|
export declare class Auth implements AuthService {
|
|
23
29
|
private readonly timing;
|
|
24
30
|
private readonly cookieName;
|
|
@@ -26,87 +32,92 @@ export declare class Auth implements AuthService {
|
|
|
26
32
|
private readonly kv;
|
|
27
33
|
private readonly repository;
|
|
28
34
|
private readonly turnstileSecretKey?;
|
|
29
|
-
private readonly
|
|
30
|
-
private readonly
|
|
35
|
+
private readonly emailCodes;
|
|
36
|
+
private readonly desktopCodes;
|
|
31
37
|
private readonly oauth2Client;
|
|
32
38
|
private readonly onetapRequireNonce;
|
|
33
39
|
private readonly touchInterval;
|
|
40
|
+
private readonly sessionHeader;
|
|
34
41
|
private readonly ATTR_OAUTH2_AUTHORIZATION_REQUEST;
|
|
35
42
|
constructor({ sessionRepository, kvRepository, turnstileSecretKey, verificationCode, oauth2, cookie, session, timing }: AuthConfig);
|
|
36
43
|
private getSessionId;
|
|
44
|
+
/** The session the request carries, if it exists. */
|
|
45
|
+
private loadSession;
|
|
46
|
+
/** The signed-in principal of a session, if any. */
|
|
47
|
+
private principalOf;
|
|
48
|
+
/**
|
|
49
|
+
* A new session signed in as `principal`. With `carryOver`, what the host kept in the pre-login
|
|
50
|
+
* session moves to the new one; the authorization request (state, PKCE verifier, flow
|
|
51
|
+
* attributes) is single-use and was consumed, and a principal left there from an earlier
|
|
52
|
+
* sign-in is replaced. The new id is never the one the browser brought, which may have been
|
|
53
|
+
* planted by an attacker who started the flow (session fixation).
|
|
54
|
+
*/
|
|
55
|
+
private signIn;
|
|
56
|
+
/**
|
|
57
|
+
* `response` carrying `session`: its cookie, renewed for the whole inactive interval, and with
|
|
58
|
+
* `session.header` on, the `X-Auth-Token` header a client without a cookie jar reads.
|
|
59
|
+
*/
|
|
60
|
+
private withSession;
|
|
61
|
+
/** Runs a JSON route: a failure it knows is its response, anything else is the host's. */
|
|
62
|
+
private guard;
|
|
63
|
+
/** The error redirect of the callback flow; `refusal`'s reason and metadata ride along. */
|
|
64
|
+
private redirect;
|
|
37
65
|
csrf: (_request: Request) => Promise<Response>;
|
|
38
66
|
logout: (request: Request) => Promise<Response>;
|
|
39
67
|
logged: (request: Request, onLogged?: LoggedHandler) => Promise<Response>;
|
|
40
68
|
private createPkceParameters;
|
|
41
69
|
private getOauth2StateKey;
|
|
42
70
|
private getOauth2NonceKey;
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
71
|
+
/**
|
|
72
|
+
* Spends a nonce `oauth2Nonce` issued for `provider`: `NONCE_MISMATCH` for one that was not,
|
|
73
|
+
* or was used already. An id token is replayable until it expires; the nonce it carries makes
|
|
74
|
+
* it sign in once.
|
|
75
|
+
*/
|
|
76
|
+
private consumeNonce;
|
|
48
77
|
oauth2State: (request: Request) => Promise<Response>;
|
|
49
78
|
oauth2Nonce: (request: Request) => Promise<Response>;
|
|
50
79
|
oauth2Authorization: (request: Request, options?: OAuth2AuthorizationOptions) => Promise<Response>;
|
|
51
|
-
private readonly redirect;
|
|
52
80
|
loginOAuth2Code: (request: Request, handler: AuthorizedHandler) => Promise<Response>;
|
|
53
|
-
/**
|
|
54
|
-
|
|
55
|
-
* authorization request (state, PKCE verifier, flow attributes) is single-use and was consumed
|
|
56
|
-
* by this callback, and a principal left there from an earlier sign-in is replaced.
|
|
57
|
-
*/
|
|
58
|
-
private signedInSession;
|
|
81
|
+
/** The shared tail of the JSON login routes: the host's handler, then a signed-in session. */
|
|
82
|
+
private authorize;
|
|
59
83
|
loginOAuth2Native: (request: Request, handler: AuthorizedHandler) => Promise<Response>;
|
|
60
84
|
loginOAuth2Onetap: (request: Request, handler: AuthorizedHandler, provider?: string) => Promise<Response>;
|
|
61
|
-
sendEmailVerificationCode: (request: Request, sender:
|
|
62
|
-
|
|
63
|
-
verificationCode: string;
|
|
64
|
-
}) => Promise<void>) => Promise<Response>;
|
|
85
|
+
sendEmailVerificationCode: (request: Request, sender: EmailSender) => Promise<Response>;
|
|
86
|
+
verifyEmailCode: (email: string, verificationCode: string) => Promise<void>;
|
|
65
87
|
loginEmail: (request: Request, handler: AuthorizedHandler) => Promise<Response>;
|
|
66
88
|
/**
|
|
67
|
-
* Step 1 of the desktop / native app sign-in handshake. The caller already
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
* desktop client via a loopback redirect.
|
|
89
|
+
* Step 1 of the desktop / native app sign-in handshake. The caller already completed normal
|
|
90
|
+
* sign-in on the web (so a cookie session exists); this issues a 5-minute single-use code that
|
|
91
|
+
* the web page hands off to the desktop client via a loopback redirect.
|
|
71
92
|
*
|
|
72
|
-
* PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier`
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
* the
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
* capture on loopback) from exchanging it.
|
|
93
|
+
* PKCE (RFC 7636, S256 only): the desktop generates a random `code_verifier` before opening the
|
|
94
|
+
* browser, derives `code_challenge = base64url(sha256(verifier))`, and threads the challenge
|
|
95
|
+
* through the URL → web page → here. The challenge is pinned to the code; `desktopExchange`
|
|
96
|
+
* enforces that the caller knows the matching verifier. This blocks an attacker who only
|
|
97
|
+
* intercepts the auth code (browser history, malicious extension, packet capture on loopback)
|
|
98
|
+
* from exchanging it.
|
|
79
99
|
*
|
|
80
|
-
* Requires an authenticated cookie session
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* upstream sign-in flow already passed CAPTCHA / OAuth / verification.
|
|
100
|
+
* Requires an authenticated cookie session: anonymous callers get 401. Throttling / abuse
|
|
101
|
+
* protection on this endpoint is the API layer's job (rate-limit per principal); the SDK does
|
|
102
|
+
* not gate it because the upstream sign-in flow already passed CAPTCHA / OAuth / verification.
|
|
84
103
|
*/
|
|
85
104
|
desktopAuthorize: (request: Request) => Promise<Response>;
|
|
86
105
|
/**
|
|
87
|
-
* Step 2 of the desktop / native app sign-in handshake. The desktop
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
* either side doesn't cascade to the other.
|
|
106
|
+
* Step 2 of the desktop / native app sign-in handshake. The desktop client hands back the auth
|
|
107
|
+
* code it received via the loopback callback, and we mint a fresh session for it, independent
|
|
108
|
+
* of the web cookie session that originally authorized the code, so logging out on either side
|
|
109
|
+
* does not cascade to the other.
|
|
92
110
|
*
|
|
93
|
-
* Cookie attributes:
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
98
|
-
* backend this combination is rejected by Chromium — see the desktop
|
|
99
|
-
* sign-in PRD for workarounds.
|
|
111
|
+
* Cookie attributes: `SameSite=None; Secure` so the cookie survives cross-site fetches from the
|
|
112
|
+
* desktop renderer's `file://` / `app://` origin. The configured `cookieOptions` provide
|
|
113
|
+
* everything else (`domain`, `path`, `httpOnly`); the override is targeted at the
|
|
114
|
+
* SameSite/Secure pair only. In a non-HTTPS dev backend this combination is rejected by
|
|
115
|
+
* Chromium, see the desktop sign-in PRD for workarounds.
|
|
100
116
|
*
|
|
101
|
-
* No CSRF check needed
|
|
102
|
-
*
|
|
117
|
+
* No CSRF check needed: the request carries no cookie, the auth code itself is the credential,
|
|
118
|
+
* and the code's single-use removal blocks replay.
|
|
103
119
|
*/
|
|
104
120
|
desktopExchange: (request: Request) => Promise<Response>;
|
|
105
|
-
sendPhoneVerificationCode: (_request: Request, _sender: (data: {
|
|
106
|
-
phone: string;
|
|
107
|
-
verificationCode: string;
|
|
108
|
-
}) => Promise<void>) => Promise<Response>;
|
|
109
|
-
loginPhone: (_request: Request, _handler: AuthorizedHandler) => Promise<Response>;
|
|
110
121
|
kick: (principal: Principal) => Promise<void>;
|
|
111
122
|
/**
|
|
112
123
|
* Sliding expiration: an authenticated request renews the session, as `logged` does, so a user
|
|
@@ -116,10 +127,10 @@ export declare class Auth implements AuthService {
|
|
|
116
127
|
* `AuthConfig`), so a session accessed again within the interval is not rewritten.
|
|
117
128
|
*/
|
|
118
129
|
private touch;
|
|
130
|
+
getPrincipal: (request: Request) => Promise<Principal | null>;
|
|
119
131
|
isAuthenticated: (request: Request) => Promise<boolean>;
|
|
120
132
|
getSession: <T extends boolean>(request: Request, create: T) => Promise<T extends true ? Session : Session | null>;
|
|
121
133
|
deleteSession: (sessionId: string) => Promise<void>;
|
|
122
|
-
getPrincipal: (request: Request) => Promise<Principal | null>;
|
|
123
134
|
listSessions: (principal: Principal) => Promise<Session[]>;
|
|
124
135
|
cleanupExpiredSessions: (cleanupCount?: number) => Promise<Response>;
|
|
125
136
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/auth/index.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/auth/index.ts"],"mappings":";;;;qBAyHa;WACX;WACA;WACA;WAEA;WACA;WACA;WACA;WACA;WACA;WAEA;WACA;WASA;WACA;WAEA;;;;;;;;;qBAUW,gBAAgB;mBACV;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;mBACA;EAEjB,cACE,mBACA,cACA,oBACA,kBACA,QACA,QACA,SACA,UACC;UA6BK;;UAOM;;UAMN;;;;;;;;UAYM;;;;;UAiBN;;UAYM;;UAWN;EAqBR,OAAI,UAAoB,YAAU,QAAQ;EAmB1C,SAAM,SAAmB,YAAU,QAAQ;EAU3C,SAAM,SAAmB,SAAO,WAAa,kBAAgB,QAAQ;UAc7D;UAWA;UAIA;;;;;;UASM;EAMd,cAAW,SAAmB,YAAU,QAAQ;EAOhD,cAAW,SAAmB,YAAU,QAAQ;EAOhD,sBAAmB,SACR,SAAO,UACN,+BACT,QAAQ;EAqCX,kBAAe,SAAmB,SAAO,SAAW,sBAAoB,QAAQ;;UAoGlE;EAad,oBAAiB,SAAa,SAAO,SAAW,sBAAoB,QAAQ;EAwC5E,oBAAiB,SACN,SAAO,SACP,mBAAiB,sBAEzB,QAAQ;EAqDX,4BAAyB,SAAa,SAAO,QAAU,gBAAc,QAAQ;EAY7E,kBAAe,eAAiB,6BAA6B;EAG7D,aAAU,SAAa,SAAO,SAAW,sBAAoB,QAAQ;;;;;;;;;;;;;;;;;EAoCrE,mBAAgB,SAAa,YAAU,QAAQ;;;;;;;;;;;;;;;;EAwB/C,kBAAe,SAAa,YAAU,QAAQ;EAe9C,OAAI,WAAqB,cAAY;;;;;;;;UAcvB;EAOd,eAAY,SAAmB,YAAU,QAAQ;EAQjD,kBAAe,SAAmB,YAAU;EAI5C,aAAoB,mBAAiB,SAC1B,SAAO,QACR,MACP,QAAQ,iBAAiB,UAAU;EAStC,gBAAa,sBAA8B;EAI3C,eAAY,WAAqB,cAAY,QAAQ;EAKrD,yBAAsB,0BAAkC,QAAQ"}
|