@schemavaults/auth-common 0.10.11 → 0.10.15
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/dist/index.d.ts +3 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/oauth2-state-schema.d.ts +29 -0
- package/dist/oauth2-state-schema.js +71 -0
- package/dist/oauth2-state-schema.js.map +1 -0
- package/dist/timing-safe-string-equal.d.ts +13 -0
- package/dist/timing-safe-string-equal.js +24 -0
- package/dist/timing-safe-string-equal.js.map +1 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -28,3 +28,6 @@ export { determineRefreshTokenCookieSameSiteValue } from "./determineRefreshToke
|
|
|
28
28
|
export { authorizeClientApplicationFormType } from "./authorize-client-application-form-type";
|
|
29
29
|
export { paginationOptionsSchema, DEFAULT_PAGINATION_PAGE_INDEX, DEFAULT_PAGINATION_PAGE_SIZE, isValidPaginationOptions, } from "./pagination";
|
|
30
30
|
export type { PaginationOptions } from "./pagination";
|
|
31
|
+
export { timingSafeStringEqual } from "./timing-safe-string-equal";
|
|
32
|
+
export { oauth2StateSchema, OAUTH2_STATE_VSCHAR_REGEX, parseOAuth2State, OAuth2StateValidationError, } from "./oauth2-state-schema";
|
|
33
|
+
export type { OAuth2State } from "./oauth2-state-schema";
|
package/dist/index.js
CHANGED
|
@@ -19,4 +19,6 @@ export { AccessTokenCookieName, AccessTokenExpiryCookieName, } from "./AccessTok
|
|
|
19
19
|
export { determineRefreshTokenCookieSameSiteValue } from "./determineRefreshTokenCookieSameSiteValue";
|
|
20
20
|
export { authorizeClientApplicationFormType } from "./authorize-client-application-form-type";
|
|
21
21
|
export { paginationOptionsSchema, DEFAULT_PAGINATION_PAGE_INDEX, DEFAULT_PAGINATION_PAGE_SIZE, isValidPaginationOptions, } from "./pagination";
|
|
22
|
+
export { timingSafeStringEqual } from "./timing-safe-string-equal";
|
|
23
|
+
export { oauth2StateSchema, OAUTH2_STATE_VSCHAR_REGEX, parseOAuth2State, OAuth2StateValidationError, } from "./oauth2-state-schema";
|
|
22
24
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,cAAc,GACf,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,cAAc,EAAiB,MAAM,aAAa,CAAC;AAE5D,cAAc,eAAe,CAAC;AAG9B,mBAAmB;AACnB,cAAc,cAAc,CAAC;AAM7B,cAAc,cAAc,CAAC;AAG7B,cAAc,QAAQ,CAAC;AAGvB,OAAO,EACL,wBAAwB,GAEzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,yBAAyB,EAEzB,uCAAuC,GAExC,MAAM,yBAAyB,CAAC;AAEjC,cAAc,mCAAmC,CAAC;AAGlD,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,EACL,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,EACzB,4BAA4B,EAC5B,aAAa,EACb,4BAA4B,EAC5B,0BAA0B,EAC1B,8BAA8B,EAC9B,8BAA8B,EAC9B,sBAAsB,EACtB,sBAAsB,EACtB,iCAAiC,EACjC,kCAAkC,EAClC,4BAA4B,EAC5B,+BAA+B,EAC/B,oCAAoC,EACpC,qCAAqC,GACtC,MAAM,iBAAiB,CAAC;AAczB,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EACL,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,wCAAwC,EAAE,MAAM,4CAA4C,CAAC;AAEtG,OAAO,EAAE,kCAAkC,EAAE,MAAM,0CAA0C,CAAC;AAE9F,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAC7B,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,cAAc,GACf,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,cAAc,EAAiB,MAAM,aAAa,CAAC;AAE5D,cAAc,eAAe,CAAC;AAG9B,mBAAmB;AACnB,cAAc,cAAc,CAAC;AAM7B,cAAc,cAAc,CAAC;AAG7B,cAAc,QAAQ,CAAC;AAGvB,OAAO,EACL,wBAAwB,GAEzB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,yBAAyB,EAEzB,uCAAuC,GAExC,MAAM,yBAAyB,CAAC;AAEjC,cAAc,mCAAmC,CAAC;AAGlD,OAAO,EAAE,0BAA0B,EAAE,MAAM,+BAA+B,CAAC;AAC3E,OAAO,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAE5D,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAEtE,OAAO,EACL,sBAAsB,EACtB,0BAA0B,GAC3B,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,yBAAyB,EACzB,4BAA4B,EAC5B,aAAa,EACb,4BAA4B,EAC5B,0BAA0B,EAC1B,8BAA8B,EAC9B,8BAA8B,EAC9B,sBAAsB,EACtB,sBAAsB,EACtB,iCAAiC,EACjC,kCAAkC,EAClC,4BAA4B,EAC5B,+BAA+B,EAC/B,oCAAoC,EACpC,qCAAqC,GACtC,MAAM,iBAAiB,CAAC;AAczB,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AAEtE,OAAO,EACL,sBAAsB,EACtB,4BAA4B,GAC7B,MAAM,2BAA2B,CAAC;AAEnC,OAAO,EACL,qBAAqB,EACrB,2BAA2B,GAC5B,MAAM,0BAA0B,CAAC;AAElC,OAAO,EAAE,wCAAwC,EAAE,MAAM,4CAA4C,CAAC;AAEtG,OAAO,EAAE,kCAAkC,EAAE,MAAM,0CAA0C,CAAC;AAE9F,OAAO,EACL,uBAAuB,EACvB,6BAA6B,EAC7B,4BAA4B,EAC5B,wBAAwB,GACzB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAEnE,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,gBAAgB,EAChB,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const OAUTH2_STATE_VSCHAR_REGEX: RegExp;
|
|
3
|
+
export declare const oauth2StateSchema: z.ZodString;
|
|
4
|
+
export type OAuth2State = z.infer<typeof oauth2StateSchema>;
|
|
5
|
+
/**
|
|
6
|
+
* Thrown by `parseOAuth2State` when the caller supplied a `state`
|
|
7
|
+
* value that is present but fails schema validation. Callers at
|
|
8
|
+
* server-side entry points should turn this into a 400 response
|
|
9
|
+
* (e.g. `redirectWithError(400, "bad_request")` for page routes,
|
|
10
|
+
* `NextResponse.json(..., { status: 400 })` for API routes).
|
|
11
|
+
*/
|
|
12
|
+
export declare class OAuth2StateValidationError extends Error {
|
|
13
|
+
readonly reasons: readonly string[];
|
|
14
|
+
constructor(reasons: readonly string[]);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Validates an OAuth2 `state` at a server boundary.
|
|
18
|
+
*
|
|
19
|
+
* - Absent / null / undefined → returns `null` (RFC-legal; `state`
|
|
20
|
+
* is optional).
|
|
21
|
+
* - Present + well-formed → returns the validated string.
|
|
22
|
+
* - Present + malformed (wrong type, empty, too long, non-VSCHAR)
|
|
23
|
+
* → throws `OAuth2StateValidationError` so the caller can 400.
|
|
24
|
+
*
|
|
25
|
+
* Empty strings are treated as malformed (not absent) so a client
|
|
26
|
+
* that accidentally serializes `?state=` gets a loud signal instead
|
|
27
|
+
* of a silent downgrade.
|
|
28
|
+
*/
|
|
29
|
+
export declare function parseOAuth2State(raw: unknown): string | null;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
// RFC 6749 §10.12 introduces the OAuth2 `state` parameter as a
|
|
3
|
+
// client-side CSRF nonce. RFC 6749 §A.5 defines its on-the-wire form
|
|
4
|
+
// as `*VSCHAR` — zero or more printable ASCII characters %x20-%x7E.
|
|
5
|
+
// The auth server never interprets `state`; it only echoes the value
|
|
6
|
+
// untouched on the callback. The validator below therefore captures
|
|
7
|
+
// only the properties worth enforcing at the protocol boundary:
|
|
8
|
+
//
|
|
9
|
+
// 1. Non-empty (a zero-length state has no CSRF value).
|
|
10
|
+
// 2. Bounded length — 512 chars is comfortably above common shapes
|
|
11
|
+
// (UUID, base64url 32-byte nonce, short signed JWTs) while
|
|
12
|
+
// protecting log pipelines and callback-URL buffers from
|
|
13
|
+
// unbounded echo.
|
|
14
|
+
// 3. Printable ASCII only — prevents log-pollution / terminal-escape
|
|
15
|
+
// smuggling via control chars, NULs, and CR/LF.
|
|
16
|
+
//
|
|
17
|
+
// This schema is deliberately not base64url-specific: that would be
|
|
18
|
+
// SDK-opinionated and would break RFC-legal third-party clients that
|
|
19
|
+
// use UUIDs, signed JWTs, or other formats for their state value.
|
|
20
|
+
const MAX_OAUTH2_STATE_LENGTH = 512;
|
|
21
|
+
export const OAUTH2_STATE_VSCHAR_REGEX = /^[\x20-\x7E]+$/;
|
|
22
|
+
export const oauth2StateSchema = z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.max(MAX_OAUTH2_STATE_LENGTH)
|
|
26
|
+
.regex(OAUTH2_STATE_VSCHAR_REGEX, {
|
|
27
|
+
message: "OAuth2 'state' must contain only printable ASCII (RFC 6749 §A.5 VSCHAR)",
|
|
28
|
+
});
|
|
29
|
+
/**
|
|
30
|
+
* Thrown by `parseOAuth2State` when the caller supplied a `state`
|
|
31
|
+
* value that is present but fails schema validation. Callers at
|
|
32
|
+
* server-side entry points should turn this into a 400 response
|
|
33
|
+
* (e.g. `redirectWithError(400, "bad_request")` for page routes,
|
|
34
|
+
* `NextResponse.json(..., { status: 400 })` for API routes).
|
|
35
|
+
*/
|
|
36
|
+
export class OAuth2StateValidationError extends Error {
|
|
37
|
+
reasons;
|
|
38
|
+
constructor(reasons) {
|
|
39
|
+
super(`Invalid OAuth2 'state' parameter: ${reasons.join("; ")}`);
|
|
40
|
+
this.name = "OAuth2StateValidationError";
|
|
41
|
+
this.reasons = reasons;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Validates an OAuth2 `state` at a server boundary.
|
|
46
|
+
*
|
|
47
|
+
* - Absent / null / undefined → returns `null` (RFC-legal; `state`
|
|
48
|
+
* is optional).
|
|
49
|
+
* - Present + well-formed → returns the validated string.
|
|
50
|
+
* - Present + malformed (wrong type, empty, too long, non-VSCHAR)
|
|
51
|
+
* → throws `OAuth2StateValidationError` so the caller can 400.
|
|
52
|
+
*
|
|
53
|
+
* Empty strings are treated as malformed (not absent) so a client
|
|
54
|
+
* that accidentally serializes `?state=` gets a loud signal instead
|
|
55
|
+
* of a silent downgrade.
|
|
56
|
+
*/
|
|
57
|
+
export function parseOAuth2State(raw) {
|
|
58
|
+
if (raw === undefined || raw === null)
|
|
59
|
+
return null;
|
|
60
|
+
if (typeof raw !== "string") {
|
|
61
|
+
throw new OAuth2StateValidationError([
|
|
62
|
+
`Expected string, got ${Array.isArray(raw) ? "array" : typeof raw}`,
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
const parsed = oauth2StateSchema.safeParse(raw);
|
|
66
|
+
if (!parsed.success) {
|
|
67
|
+
throw new OAuth2StateValidationError(parsed.error.issues.map((i) => i.message));
|
|
68
|
+
}
|
|
69
|
+
return parsed.data;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=oauth2-state-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth2-state-schema.js","sourceRoot":"","sources":["../src/oauth2-state-schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,+DAA+D;AAC/D,qEAAqE;AACrE,oEAAoE;AACpE,qEAAqE;AACrE,oEAAoE;AACpE,gEAAgE;AAChE,EAAE;AACF,0DAA0D;AAC1D,qEAAqE;AACrE,gEAAgE;AAChE,8DAA8D;AAC9D,uBAAuB;AACvB,uEAAuE;AACvE,qDAAqD;AACrD,EAAE;AACF,oEAAoE;AACpE,qEAAqE;AACrE,kEAAkE;AAElE,MAAM,uBAAuB,GAAG,GAAY,CAAC;AAE7C,MAAM,CAAC,MAAM,yBAAyB,GAAW,gBAAgB,CAAC;AAElE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,EAAE;KACR,GAAG,CAAC,CAAC,CAAC;KACN,GAAG,CAAC,uBAAuB,CAAC;KAC5B,KAAK,CAAC,yBAAyB,EAAE;IAChC,OAAO,EACL,yEAAyE;CAC5E,CAAC,CAAC;AAIL;;;;;;GAMG;AACH,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACnC,OAAO,CAAoB;IAC3C,YAAmB,OAA0B;QAC3C,KAAK,CAAC,qCAAqC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjE,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;CACF;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,gBAAgB,CAAC,GAAY;IAC3C,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IACnD,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE,CAAC;QAC5B,MAAM,IAAI,0BAA0B,CAAC;YACnC,wBAAwB,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,GAAG,EAAE;SACpE,CAAC,CAAC;IACL,CAAC;IACD,MAAM,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;IAChD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,IAAI,0BAA0B,CAClC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAC1C,CAAC;IACJ,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timing-safe string comparison usable from browser bundles.
|
|
3
|
+
*
|
|
4
|
+
* `node:crypto.timingSafeEqual` is Node-only; this pure-JS variant
|
|
5
|
+
* operates on strings and runs in both browser and server contexts.
|
|
6
|
+
* Not strictly constant-time under every JIT, but avoids early-exit on
|
|
7
|
+
* the first byte of divergence — strictly better than `===` for
|
|
8
|
+
* CSRF-nonce / token comparison.
|
|
9
|
+
*
|
|
10
|
+
* Returns false on any non-string input or length mismatch.
|
|
11
|
+
*/
|
|
12
|
+
export declare function timingSafeStringEqual(a: string, b: string): boolean;
|
|
13
|
+
export default timingSafeStringEqual;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Timing-safe string comparison usable from browser bundles.
|
|
3
|
+
*
|
|
4
|
+
* `node:crypto.timingSafeEqual` is Node-only; this pure-JS variant
|
|
5
|
+
* operates on strings and runs in both browser and server contexts.
|
|
6
|
+
* Not strictly constant-time under every JIT, but avoids early-exit on
|
|
7
|
+
* the first byte of divergence — strictly better than `===` for
|
|
8
|
+
* CSRF-nonce / token comparison.
|
|
9
|
+
*
|
|
10
|
+
* Returns false on any non-string input or length mismatch.
|
|
11
|
+
*/
|
|
12
|
+
export function timingSafeStringEqual(a, b) {
|
|
13
|
+
if (typeof a !== "string" || typeof b !== "string")
|
|
14
|
+
return false;
|
|
15
|
+
if (a.length !== b.length)
|
|
16
|
+
return false;
|
|
17
|
+
let diff = 0;
|
|
18
|
+
for (let i = 0; i < a.length; i++) {
|
|
19
|
+
diff |= a.charCodeAt(i) ^ b.charCodeAt(i);
|
|
20
|
+
}
|
|
21
|
+
return diff === 0;
|
|
22
|
+
}
|
|
23
|
+
export default timingSafeStringEqual;
|
|
24
|
+
//# sourceMappingURL=timing-safe-string-equal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"timing-safe-string-equal.js","sourceRoot":"","sources":["../src/timing-safe-string-equal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,qBAAqB,CAAC,CAAS,EAAE,CAAS;IACxD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IACjE,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACxC,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAClC,IAAI,IAAI,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAC5C,CAAC;IACD,OAAO,IAAI,KAAK,CAAC,CAAC;AACpB,CAAC;AAED,eAAe,qBAAqB,CAAC"}
|
package/package.json
CHANGED