@zudojs/auth-oauth 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +251 -0
  3. package/dist/index.d.ts +18 -0
  4. package/dist/index.js +18 -0
  5. package/dist/oauthClient/index.d.ts +10 -0
  6. package/dist/oauthClient/index.js +10 -0
  7. package/dist/oauthClient/oauthAuthorize.core.d.ts +34 -0
  8. package/dist/oauthClient/oauthAuthorize.core.js +93 -0
  9. package/dist/oauthClient/oauthConfig.resolve.d.ts +60 -0
  10. package/dist/oauthClient/oauthConfig.resolve.js +140 -0
  11. package/dist/oauthClient/oauthHttp.core.d.ts +43 -0
  12. package/dist/oauthClient/oauthHttp.core.js +137 -0
  13. package/dist/oauthClient/oauthToken.core.d.ts +50 -0
  14. package/dist/oauthClient/oauthToken.core.js +171 -0
  15. package/dist/oauthClient/oauthUserInfo.core.d.ts +27 -0
  16. package/dist/oauthClient/oauthUserInfo.core.js +104 -0
  17. package/dist/oauthErrors/index.d.ts +7 -0
  18. package/dist/oauthErrors/index.js +7 -0
  19. package/dist/oauthErrors/oauthError.base.d.ts +113 -0
  20. package/dist/oauthErrors/oauthError.base.js +168 -0
  21. package/dist/oauthProviders/index.d.ts +7 -0
  22. package/dist/oauthProviders/index.js +7 -0
  23. package/dist/oauthProviders/oauthProvider.presets.d.ts +53 -0
  24. package/dist/oauthProviders/oauthProvider.presets.js +190 -0
  25. package/dist/oauthSecurity/index.d.ts +10 -0
  26. package/dist/oauthSecurity/index.js +10 -0
  27. package/dist/oauthSecurity/oauthJson.sanitize.d.ts +38 -0
  28. package/dist/oauthSecurity/oauthJson.sanitize.js +85 -0
  29. package/dist/oauthSecurity/oauthPkce.core.d.ts +34 -0
  30. package/dist/oauthSecurity/oauthPkce.core.js +47 -0
  31. package/dist/oauthSecurity/oauthState.core.d.ts +31 -0
  32. package/dist/oauthSecurity/oauthState.core.js +44 -0
  33. package/dist/oauthSecurity/oauthUrl.guard.d.ts +47 -0
  34. package/dist/oauthSecurity/oauthUrl.guard.js +190 -0
  35. package/dist/oauthTypes/index.d.ts +7 -0
  36. package/dist/oauthTypes/index.js +7 -0
  37. package/dist/oauthTypes/oauth.type.d.ts +165 -0
  38. package/dist/oauthTypes/oauth.type.js +7 -0
  39. package/package.json +58 -0
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Anti-CSRF `state` generation and timing-safe verification.
3
+ *
4
+ * @module oauthSecurity/oauthState
5
+ *
6
+ * Without `state`, an attacker can complete their own authorization at your
7
+ * provider and then feed the resulting `code` to a victim's callback, binding
8
+ * the victim's session to the attacker's identity. `state` is mandatory here,
9
+ * and the comparison is timing-safe so the value cannot be recovered a byte
10
+ * at a time.
11
+ */
12
+ /**
13
+ * Generate a random `state` value (256 bits, base64url).
14
+ *
15
+ * @returns A 43-character URL-safe string.
16
+ */
17
+ export declare function generateState(): string;
18
+ /**
19
+ * Compare the issued `state` with the one returned on the callback, without
20
+ * leaking the answer through timing.
21
+ *
22
+ * Lengths are compared first (`timingSafeEqual` throws on unequal buffers);
23
+ * a length difference is not secret, the contents are. Empty or non-string
24
+ * inputs are always `false` — an absent `state` never passes.
25
+ *
26
+ * @param expected - The state you issued and stored.
27
+ * @param received - The `state` query parameter from the callback.
28
+ * @returns `true` only if both are non-empty strings with identical bytes.
29
+ */
30
+ export declare function verifyState(expected: string, received: string): boolean;
31
+ //# sourceMappingURL=oauthState.core.d.ts.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Anti-CSRF `state` generation and timing-safe verification.
3
+ *
4
+ * @module oauthSecurity/oauthState
5
+ *
6
+ * Without `state`, an attacker can complete their own authorization at your
7
+ * provider and then feed the resulting `code` to a victim's callback, binding
8
+ * the victim's session to the attacker's identity. `state` is mandatory here,
9
+ * and the comparison is timing-safe so the value cannot be recovered a byte
10
+ * at a time.
11
+ */
12
+ import { randomBytes, timingSafeEqual } from "node:crypto";
13
+ /**
14
+ * Generate a random `state` value (256 bits, base64url).
15
+ *
16
+ * @returns A 43-character URL-safe string.
17
+ */
18
+ export function generateState() {
19
+ return randomBytes(32).toString("base64url");
20
+ }
21
+ /**
22
+ * Compare the issued `state` with the one returned on the callback, without
23
+ * leaking the answer through timing.
24
+ *
25
+ * Lengths are compared first (`timingSafeEqual` throws on unequal buffers);
26
+ * a length difference is not secret, the contents are. Empty or non-string
27
+ * inputs are always `false` — an absent `state` never passes.
28
+ *
29
+ * @param expected - The state you issued and stored.
30
+ * @param received - The `state` query parameter from the callback.
31
+ * @returns `true` only if both are non-empty strings with identical bytes.
32
+ */
33
+ export function verifyState(expected, received) {
34
+ if (typeof expected !== "string" || typeof received !== "string")
35
+ return false;
36
+ if (expected.length === 0 || received.length === 0)
37
+ return false;
38
+ const a = Buffer.from(expected, "utf8");
39
+ const b = Buffer.from(received, "utf8");
40
+ if (a.length !== b.length)
41
+ return false;
42
+ return timingSafeEqual(a, b);
43
+ }
44
+ //# sourceMappingURL=oauthState.core.js.map
@@ -0,0 +1,47 @@
1
+ /**
2
+ * URL validation and SSRF guards.
3
+ *
4
+ * @module oauthSecurity/oauthUrl
5
+ *
6
+ * An `OAuthConfig` is frequently operator- or tenant-supplied, and the token
7
+ * and user-info URLs are fetched *by your server*. That makes them a
8
+ * server-side request forgery sink: an attacker who can set `tokenUrl` to
9
+ * `http://169.254.169.254/latest/meta-data/iam/...` would have your process
10
+ * fetch cloud credentials and hand the body back. Every URL is therefore
11
+ * checked before any request is made.
12
+ *
13
+ * Two policies:
14
+ *
15
+ * - **Browser-facing URLs** (`authorizeUrl`, redirect URIs) must be `https`,
16
+ * or `http` when the host is exactly `localhost` / `127.0.0.1` / `[::1]`
17
+ * (local development), and must not embed credentials.
18
+ * - **Server-fetched URLs** (`tokenUrl`, `userInfoUrl`) must additionally be
19
+ * `https` unconditionally and resolve — by literal inspection — to a public
20
+ * host: no loopback, private, carrier-grade-NAT, link-local, unique-local,
21
+ * multicast, reserved or cloud-metadata address, and no `localhost`,
22
+ * `*.local`, `*.internal` or `metadata.google.internal` name.
23
+ *
24
+ * **Known limit.** These checks are on the literal host in the URL. They do
25
+ * not resolve DNS, so a hostname that resolves to a private address (DNS
26
+ * rebinding) is not caught here. Pair this with network egress controls if
27
+ * you accept endpoint URLs from untrusted operators.
28
+ */
29
+ /**
30
+ * Whether a host literal is one this package refuses to fetch from.
31
+ *
32
+ * Exported for the SSRF tests; not part of the supported surface.
33
+ */
34
+ export declare function isBlockedFetchHost(hostname: string): boolean;
35
+ /** What a URL is used for, which decides how strict the check is. */
36
+ export type UrlUse = "browser" | "fetch";
37
+ /**
38
+ * Validate a URL and return its parsed, normalised form.
39
+ *
40
+ * @param raw - The URL string from the configuration.
41
+ * @param label - Field name, used only in the (secret-free) error message.
42
+ * @param use - `browser` for redirect targets, `fetch` for endpoints your
43
+ * server calls (adds the SSRF host policy and forbids `http` entirely).
44
+ * @throws {OAuthEndpointNotAllowedError} If the URL fails any check.
45
+ */
46
+ export declare function assertSafeUrl(raw: string, label: string, use: UrlUse): URL;
47
+ //# sourceMappingURL=oauthUrl.guard.d.ts.map
@@ -0,0 +1,190 @@
1
+ /**
2
+ * URL validation and SSRF guards.
3
+ *
4
+ * @module oauthSecurity/oauthUrl
5
+ *
6
+ * An `OAuthConfig` is frequently operator- or tenant-supplied, and the token
7
+ * and user-info URLs are fetched *by your server*. That makes them a
8
+ * server-side request forgery sink: an attacker who can set `tokenUrl` to
9
+ * `http://169.254.169.254/latest/meta-data/iam/...` would have your process
10
+ * fetch cloud credentials and hand the body back. Every URL is therefore
11
+ * checked before any request is made.
12
+ *
13
+ * Two policies:
14
+ *
15
+ * - **Browser-facing URLs** (`authorizeUrl`, redirect URIs) must be `https`,
16
+ * or `http` when the host is exactly `localhost` / `127.0.0.1` / `[::1]`
17
+ * (local development), and must not embed credentials.
18
+ * - **Server-fetched URLs** (`tokenUrl`, `userInfoUrl`) must additionally be
19
+ * `https` unconditionally and resolve — by literal inspection — to a public
20
+ * host: no loopback, private, carrier-grade-NAT, link-local, unique-local,
21
+ * multicast, reserved or cloud-metadata address, and no `localhost`,
22
+ * `*.local`, `*.internal` or `metadata.google.internal` name.
23
+ *
24
+ * **Known limit.** These checks are on the literal host in the URL. They do
25
+ * not resolve DNS, so a hostname that resolves to a private address (DNS
26
+ * rebinding) is not caught here. Pair this with network egress controls if
27
+ * you accept endpoint URLs from untrusted operators.
28
+ */
29
+ import { OAuthEndpointNotAllowedError } from "../oauthErrors/index.js";
30
+ /** Hostnames that are always refused for a server-fetched endpoint. */
31
+ const BLOCKED_HOST_NAMES = new Set([
32
+ "localhost",
33
+ "metadata",
34
+ "metadata.google.internal",
35
+ "instance-data",
36
+ ]);
37
+ /** Suffixes that are always refused for a server-fetched endpoint. */
38
+ const BLOCKED_HOST_SUFFIXES = [
39
+ ".localhost",
40
+ ".local",
41
+ ".internal",
42
+ ".home.arpa",
43
+ ];
44
+ /** Hosts for which plain `http` is tolerated on browser-facing URLs. */
45
+ const LOCAL_DEV_HOSTS = new Set([
46
+ "localhost",
47
+ "127.0.0.1",
48
+ "[::1]",
49
+ "::1",
50
+ ]);
51
+ /** Parse a dotted-quad IPv4 literal, or `undefined` if it is not one. */
52
+ function parseIpv4(host) {
53
+ const parts = host.split(".");
54
+ if (parts.length !== 4)
55
+ return undefined;
56
+ const octets = [];
57
+ for (const part of parts) {
58
+ if (!/^\d{1,3}$/.test(part))
59
+ return undefined;
60
+ const value = Number(part);
61
+ if (!Number.isInteger(value) || value < 0 || value > 255)
62
+ return undefined;
63
+ octets.push(value);
64
+ }
65
+ return octets;
66
+ }
67
+ /** Whether an IPv4 literal is outside the publicly routable space. */
68
+ function isNonPublicIpv4(octets) {
69
+ const a = octets[0] ?? 0;
70
+ const b = octets[1] ?? 0;
71
+ if (a === 0)
72
+ return true; // 0.0.0.0/8 "this network"
73
+ if (a === 10)
74
+ return true; // private
75
+ if (a === 127)
76
+ return true; // loopback
77
+ if (a === 169 && b === 254)
78
+ return true; // link-local, incl. 169.254.169.254
79
+ if (a === 172 && b >= 16 && b <= 31)
80
+ return true; // private
81
+ if (a === 192 && b === 168)
82
+ return true; // private
83
+ if (a === 192 && b === 0)
84
+ return true; // 192.0.0.0/24 IETF protocol assignments
85
+ if (a === 100 && b >= 64 && b <= 127)
86
+ return true; // 100.64.0.0/10 CGNAT
87
+ if (a === 198 && (b === 18 || b === 19))
88
+ return true; // benchmarking
89
+ if (a >= 224)
90
+ return true; // multicast + reserved + broadcast
91
+ return false;
92
+ }
93
+ /** Whether an IPv6 literal (already stripped of brackets) is non-public. */
94
+ function isNonPublicIpv6(raw) {
95
+ const host = raw.toLowerCase();
96
+ if (host === "::" || host === "::1")
97
+ return true;
98
+ // IPv4-mapped / -compatible: judge the embedded IPv4 address.
99
+ const mapped = /^::(?:ffff:)?(\d{1,3}(?:\.\d{1,3}){3})$/.exec(host);
100
+ const embedded = mapped?.[1];
101
+ if (embedded !== undefined) {
102
+ const octets = parseIpv4(embedded);
103
+ return octets === undefined ? true : isNonPublicIpv4(octets);
104
+ }
105
+ // The WHATWG URL parser rewrites `::ffff:127.0.0.1` as `::ffff:7f00:1`,
106
+ // so the hex form has to be decoded back to its embedded IPv4 address.
107
+ const hexMapped = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(host);
108
+ const high = hexMapped?.[1];
109
+ const low = hexMapped?.[2];
110
+ if (high !== undefined && low !== undefined) {
111
+ const word1 = Number.parseInt(high, 16);
112
+ const word2 = Number.parseInt(low, 16);
113
+ return isNonPublicIpv4([
114
+ (word1 >> 8) & 0xff,
115
+ word1 & 0xff,
116
+ (word2 >> 8) & 0xff,
117
+ word2 & 0xff,
118
+ ]);
119
+ }
120
+ if (/^f[cd][0-9a-f]{2}:/.test(host))
121
+ return true; // fc00::/7 unique local
122
+ if (/^fe[89ab][0-9a-f]:/.test(host))
123
+ return true; // fe80::/10 link-local
124
+ if (/^ff[0-9a-f]{2}:/.test(host))
125
+ return true; // ff00::/8 multicast
126
+ return false;
127
+ }
128
+ /**
129
+ * Whether a host literal is one this package refuses to fetch from.
130
+ *
131
+ * Exported for the SSRF tests; not part of the supported surface.
132
+ */
133
+ export function isBlockedFetchHost(hostname) {
134
+ const host = hostname.toLowerCase();
135
+ const bare = host.startsWith("[") && host.endsWith("]")
136
+ ? host.slice(1, -1)
137
+ : host;
138
+ if (BLOCKED_HOST_NAMES.has(bare))
139
+ return true;
140
+ for (const suffix of BLOCKED_HOST_SUFFIXES) {
141
+ if (bare.endsWith(suffix))
142
+ return true;
143
+ }
144
+ const octets = parseIpv4(bare);
145
+ if (octets !== undefined)
146
+ return isNonPublicIpv4(octets);
147
+ if (bare.includes(":"))
148
+ return isNonPublicIpv6(bare);
149
+ return false;
150
+ }
151
+ /**
152
+ * Validate a URL and return its parsed, normalised form.
153
+ *
154
+ * @param raw - The URL string from the configuration.
155
+ * @param label - Field name, used only in the (secret-free) error message.
156
+ * @param use - `browser` for redirect targets, `fetch` for endpoints your
157
+ * server calls (adds the SSRF host policy and forbids `http` entirely).
158
+ * @throws {OAuthEndpointNotAllowedError} If the URL fails any check.
159
+ */
160
+ export function assertSafeUrl(raw, label, use) {
161
+ if (typeof raw !== "string" || raw.trim().length === 0) {
162
+ throw new OAuthEndpointNotAllowedError(`${label} must be a non-empty URL.`);
163
+ }
164
+ let url;
165
+ try {
166
+ url = new URL(raw);
167
+ }
168
+ catch {
169
+ throw new OAuthEndpointNotAllowedError(`${label} is not a valid absolute URL.`);
170
+ }
171
+ if (url.username !== "" || url.password !== "") {
172
+ throw new OAuthEndpointNotAllowedError(`${label} must not embed credentials.`);
173
+ }
174
+ const isLocalDev = LOCAL_DEV_HOSTS.has(url.hostname.toLowerCase());
175
+ if (url.protocol === "http:") {
176
+ if (use === "fetch" || !isLocalDev) {
177
+ throw new OAuthEndpointNotAllowedError(use === "fetch"
178
+ ? `${label} must use https.`
179
+ : `${label} must use https (http is allowed only for localhost).`);
180
+ }
181
+ }
182
+ else if (url.protocol !== "https:") {
183
+ throw new OAuthEndpointNotAllowedError(`${label} must use https.`);
184
+ }
185
+ if (use === "fetch" && isBlockedFetchHost(url.hostname)) {
186
+ throw new OAuthEndpointNotAllowedError(`${label} resolves to a non-public host, which is not allowed.`);
187
+ }
188
+ return url;
189
+ }
190
+ //# sourceMappingURL=oauthUrl.guard.js.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Public types for `@zudojs/auth-oauth`.
3
+ *
4
+ * @module oauthTypes
5
+ */
6
+ export { type OAuthProvider, type ClientAuthMethod, type FetchLike, type OAuthConfig, type AuthorizationUrlOptions, type AuthorizationUrlResult, type CodeExchangeOptions, type OAuthTokenSet, type OAuthUserInfo, } from "./oauth.type.js";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Public types for `@zudojs/auth-oauth`.
3
+ *
4
+ * @module oauthTypes
5
+ */
6
+ export {} from "./oauth.type.js";
7
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Public types for the OAuth2 authorization-code client.
3
+ *
4
+ * @module oauthTypes/oauth
5
+ */
6
+ /**
7
+ * Supported OAuth2 provider identifiers.
8
+ *
9
+ * `custom` requires every endpoint URL to be supplied on the config; the
10
+ * named providers supply their own defaults (see the provider presets).
11
+ */
12
+ export type OAuthProvider = "google" | "github" | "microsoft" | "apple" | "discord" | "custom";
13
+ /**
14
+ * How the client authenticates itself at the token endpoint.
15
+ *
16
+ * - `basic` — HTTP Basic auth (`client_id:client_secret`), RFC 6749 §2.3.1.
17
+ * - `body` — `client_id` / `client_secret` form fields.
18
+ *
19
+ * Each preset picks whatever its provider actually expects; override only if
20
+ * you know your deployment differs.
21
+ */
22
+ export type ClientAuthMethod = "basic" | "body";
23
+ /**
24
+ * The `fetch` shape this package depends on.
25
+ *
26
+ * Defaults to the global `fetch`. Inject your own to add proxying, retries or
27
+ * to test without a network.
28
+ */
29
+ export type FetchLike = (input: string, init: RequestInit) => Promise<Response>;
30
+ /**
31
+ * OAuth2 provider configuration.
32
+ *
33
+ * Endpoint URLs are optional for the named providers (the preset fills them
34
+ * in) and mandatory for `custom`. Every URL — supplied or preset — is
35
+ * validated before any network call; see {@link ../oauthSecurity/oauthUrl.guard}.
36
+ */
37
+ export interface OAuthConfig {
38
+ /** Provider identifier. */
39
+ readonly provider: OAuthProvider;
40
+ /** OAuth2 client ID. */
41
+ readonly clientId: string;
42
+ /**
43
+ * OAuth2 client secret.
44
+ *
45
+ * For Apple this is the short-lived ES256 client-secret JWT you generate
46
+ * from your private key; this package does not mint it for you.
47
+ *
48
+ * Never logged, never placed in an error message or stack.
49
+ */
50
+ readonly clientSecret: string;
51
+ /** Authorization endpoint. Overrides the preset. */
52
+ readonly authorizeUrl?: string;
53
+ /** Token endpoint. Overrides the preset. */
54
+ readonly tokenUrl?: string;
55
+ /** User-info endpoint. Overrides the preset. */
56
+ readonly userInfoUrl?: string;
57
+ /**
58
+ * Exact redirect URIs this client is allowed to use.
59
+ *
60
+ * Every `redirectUri` passed to {@link createAuthorizationUrl} or
61
+ * {@link exchangeCodeForToken} must appear here verbatim (scheme and host
62
+ * compared case-insensitively, the rest byte-for-byte). At least one entry
63
+ * is required — an arbitrary redirect target is never reflected.
64
+ */
65
+ readonly allowedRedirectUris: readonly string[];
66
+ /** Requested scopes. Defaults to the preset's scopes. */
67
+ readonly scopes?: readonly string[];
68
+ /** Token-endpoint client authentication. Defaults to the preset's choice. */
69
+ readonly clientAuthMethod?: ClientAuthMethod;
70
+ /** Per-request timeout in milliseconds. Default 10000, max 120000. */
71
+ readonly timeoutMs?: number;
72
+ /**
73
+ * Hard cap on bytes read from a provider response body.
74
+ * Default 262144 (256 KiB), min 1024, max 5242880 (5 MiB).
75
+ */
76
+ readonly maxResponseBytes?: number;
77
+ /** `fetch` implementation. Defaults to the global `fetch`. */
78
+ readonly fetch?: FetchLike;
79
+ }
80
+ /** Options for {@link createAuthorizationUrl}. */
81
+ export interface AuthorizationUrlOptions {
82
+ /**
83
+ * Anti-CSRF state. **Mandatory.** Persist it against the user's session and
84
+ * check it on the callback with {@link verifyState}.
85
+ */
86
+ readonly state: string;
87
+ /** Redirect URI. Must be present in `config.allowedRedirectUris`. */
88
+ readonly redirectUri: string;
89
+ /** Scopes for this request. Defaults to `config.scopes` / the preset. */
90
+ readonly scopes?: readonly string[];
91
+ /**
92
+ * Pre-generated PKCE code verifier (43-128 chars, unreserved alphabet).
93
+ * Omit to have one generated — which is what you should normally do.
94
+ */
95
+ readonly codeVerifier?: string;
96
+ /** OIDC `nonce`, echoed into the ID token by providers that support it. */
97
+ readonly nonce?: string;
98
+ /** Extra authorization parameters (e.g. `prompt`, `access_type`). */
99
+ readonly additionalParams?: Readonly<Record<string, string>>;
100
+ }
101
+ /** Result of {@link createAuthorizationUrl}. */
102
+ export interface AuthorizationUrlResult {
103
+ /** The URL to send the user agent to. */
104
+ readonly url: string;
105
+ /** The state you supplied — persist it. */
106
+ readonly state: string;
107
+ /**
108
+ * The PKCE code verifier — persist it server-side (session, signed cookie)
109
+ * and pass it to {@link exchangeCodeForToken}. It is a secret.
110
+ */
111
+ readonly codeVerifier: string;
112
+ /** The `S256` challenge that was sent. Informational. */
113
+ readonly codeChallenge: string;
114
+ }
115
+ /** Options for {@link exchangeCodeForToken}. */
116
+ export interface CodeExchangeOptions {
117
+ /** The `code` query parameter from the callback. */
118
+ readonly code: string;
119
+ /** The verifier returned by {@link createAuthorizationUrl}. */
120
+ readonly codeVerifier: string;
121
+ /** The same redirect URI used for the authorization request. */
122
+ readonly redirectUri: string;
123
+ }
124
+ /** A validated OAuth2 token response. */
125
+ export interface OAuthTokenSet {
126
+ /** Access token. Always a non-empty string. */
127
+ readonly accessToken: string;
128
+ /** Token type as returned, defaulting to `Bearer`. */
129
+ readonly tokenType: string;
130
+ /** Lifetime in seconds, when the provider reported a numeric one. */
131
+ readonly expiresIn?: number;
132
+ /** Refresh token, when the provider issued one. */
133
+ readonly refreshToken?: string;
134
+ /** Granted scopes, split on whitespace. */
135
+ readonly scope?: readonly string[];
136
+ /** OIDC ID token, when present. Unverified — this package does not parse it. */
137
+ readonly idToken?: string;
138
+ /**
139
+ * The parsed response with prototype-polluting keys stripped.
140
+ * Contains the tokens themselves — treat as secret.
141
+ */
142
+ readonly raw: Readonly<Record<string, unknown>>;
143
+ }
144
+ /** Normalised user profile from a provider's user-info endpoint. */
145
+ export interface OAuthUserInfo {
146
+ /** The provider's stable user identifier. */
147
+ readonly providerId: string;
148
+ /**
149
+ * Email address, when the provider returned one.
150
+ *
151
+ * Optional on purpose: GitHub omits it from `/user` when the address is
152
+ * private, and Discord omits it without the `email` scope. This package
153
+ * never invents one.
154
+ */
155
+ readonly email?: string;
156
+ /** Whether the provider stated the email is verified. */
157
+ readonly emailVerified?: boolean;
158
+ /** Display name. */
159
+ readonly name?: string;
160
+ /** Avatar URL. */
161
+ readonly avatarUrl?: string;
162
+ /** Raw provider payload, prototype-polluting keys stripped. */
163
+ readonly raw?: Readonly<Record<string, unknown>>;
164
+ }
165
+ //# sourceMappingURL=oauth.type.d.ts.map
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Public types for the OAuth2 authorization-code client.
3
+ *
4
+ * @module oauthTypes/oauth
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=oauth.type.js.map
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@zudojs/auth-oauth",
3
+ "version": "1.1.0",
4
+ "description": "OAuth2 authorization-code client for the Zudojs framework — PKCE S256, mandatory state, SSRF-guarded endpoints, and provider presets for Google, GitHub, Microsoft, Apple and Discord.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "module": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js",
14
+ "default": "./dist/index.js"
15
+ }
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "!dist/**/*.map",
20
+ "!dist/**/*.tsbuildinfo",
21
+ "!dist/.tsbuildinfo"
22
+ ],
23
+ "engines": {
24
+ "node": ">=24.0.0"
25
+ },
26
+ "devDependencies": {
27
+ "typescript": "7.0.2",
28
+ "vitest": "^4.1.11",
29
+ "@types/node": "^26.4.1"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "keywords": [
35
+ "zudojs",
36
+ "oauth2",
37
+ "oauth",
38
+ "pkce",
39
+ "authorization-code",
40
+ "social-login"
41
+ ],
42
+ "homepage": "https://github.com/oyinlola-tech/zudo#readme",
43
+ "bugs": {
44
+ "url": "https://github.com/oyinlola-tech/zudo/issues"
45
+ },
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "https://github.com/oyinlola-tech/zudo",
49
+ "directory": "packages/auth-oauth"
50
+ },
51
+ "scripts": {
52
+ "build": "tsc -p tsconfig.json",
53
+ "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json --noEmit",
54
+ "clean": "rm -rf dist",
55
+ "test": "vitest run",
56
+ "test:watch": "vitest"
57
+ }
58
+ }