@svadmin/sso 0.10.0 → 0.11.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.
@@ -0,0 +1,23 @@
1
+ /**
2
+ * 浏览器端 OIDC/OAuth2 认证辅助函数。
3
+ *
4
+ * 提供 PKCE 生成、OIDC Discovery 端点解析、重定向校验、
5
+ * 反向代理 origin 恢复等通用工具。
6
+ */
7
+ /** 生成随机的 OAuth state 值 */
8
+ export declare function generateState(): string;
9
+ /** 生成 PKCE code verifier */
10
+ export declare function generateVerifier(): string;
11
+ /** 基于 code verifier 算得 code challenge (S256) */
12
+ export declare function generateChallenge(verifier: string): Promise<string>;
13
+ /**
14
+ * 校验 returnTo 重定向地址,防止开放重定向漏洞。
15
+ * 只允许同源相对路径(必须以 / 开头且非 // 开头)。
16
+ */
17
+ export declare function isValidReturnTo(url: string): boolean;
18
+ /** 从反向代理头恢复公网 origin */
19
+ export declare function getForwardedOrigin(req: Request, fallbackUrl: URL): string;
20
+ /** 通过 OIDC Discovery 解析授权端点 URL */
21
+ export declare function resolveAuthorizeUrl(issuer: string, overrideUrl?: string, fetcher?: typeof fetch): Promise<string>;
22
+ /** 通过 OIDC Discovery 解析 token 端点 URL */
23
+ export declare function resolveTokenUrl(issuer: string, overrideUrlOrFetcher?: string | typeof fetch, fetcherOverride?: typeof fetch): Promise<string>;
@@ -0,0 +1,97 @@
1
+ /**
2
+ * 浏览器端 OIDC/OAuth2 认证辅助函数。
3
+ *
4
+ * 提供 PKCE 生成、OIDC Discovery 端点解析、重定向校验、
5
+ * 反向代理 origin 恢复等通用工具。
6
+ */
7
+ /** 将 Uint8Array 编码为 base64url 格式 (纯 Web API) */
8
+ function base64url(array) {
9
+ let binary = "";
10
+ for (const byte of array)
11
+ binary += String.fromCharCode(byte);
12
+ return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
13
+ }
14
+ /** 生成随机的 OAuth state 值 */
15
+ export function generateState() {
16
+ const array = new Uint8Array(16);
17
+ crypto.getRandomValues(array);
18
+ return base64url(array);
19
+ }
20
+ /** 生成 PKCE code verifier */
21
+ export function generateVerifier() {
22
+ const array = new Uint8Array(32);
23
+ crypto.getRandomValues(array);
24
+ return base64url(array);
25
+ }
26
+ /** 基于 code verifier 算得 code challenge (S256) */
27
+ export async function generateChallenge(verifier) {
28
+ const encoder = new TextEncoder();
29
+ const data = encoder.encode(verifier);
30
+ const hash = await crypto.subtle.digest("SHA-256", data);
31
+ return base64url(new Uint8Array(hash));
32
+ }
33
+ /**
34
+ * 校验 returnTo 重定向地址,防止开放重定向漏洞。
35
+ * 只允许同源相对路径(必须以 / 开头且非 // 开头)。
36
+ */
37
+ export function isValidReturnTo(url) {
38
+ if (!url)
39
+ return false;
40
+ if (url.includes("\\"))
41
+ return false;
42
+ return /^\/[^/].*/.test(url) || url === "/";
43
+ }
44
+ /** 从反向代理头恢复公网 origin */
45
+ export function getForwardedOrigin(req, fallbackUrl) {
46
+ const forwardedProto = req.headers.get("x-forwarded-proto")?.split(",")[0]?.trim();
47
+ const forwardedHost = req.headers.get("x-forwarded-host")?.split(",")[0]?.trim();
48
+ const host = forwardedHost || req.headers.get("host") || fallbackUrl.host;
49
+ const protocol = (forwardedProto || fallbackUrl.protocol.replace(/:$/, "")).replace(/:$/, "");
50
+ return `${protocol}://${host}`;
51
+ }
52
+ /** 通过 OIDC Discovery 解析授权端点 URL */
53
+ export async function resolveAuthorizeUrl(issuer, overrideUrl = "", fetcher = fetch) {
54
+ const normalizedIssuer = issuer.replace(/\/$/, "");
55
+ const normalizedOverride = overrideUrl.trim();
56
+ if (normalizedOverride)
57
+ return normalizedOverride;
58
+ if (normalizedIssuer) {
59
+ try {
60
+ const response = await fetcher(`${normalizedIssuer}/.well-known/openid-configuration`);
61
+ if (response.ok) {
62
+ const discovery = await response.json();
63
+ if (discovery.authorization_endpoint)
64
+ return discovery.authorization_endpoint;
65
+ }
66
+ }
67
+ catch {
68
+ // 忽略 discovery 失败,走 fallback
69
+ }
70
+ }
71
+ if (normalizedIssuer)
72
+ return `${normalizedIssuer}/oauth/authorize`;
73
+ return "";
74
+ }
75
+ /** 通过 OIDC Discovery 解析 token 端点 URL */
76
+ export async function resolveTokenUrl(issuer, overrideUrlOrFetcher = "", fetcherOverride = fetch) {
77
+ const normalizedIssuer = issuer.replace(/\/$/, "");
78
+ const fetcher = typeof overrideUrlOrFetcher === "function" ? overrideUrlOrFetcher : fetcherOverride;
79
+ const overrideUrl = typeof overrideUrlOrFetcher === "string" ? overrideUrlOrFetcher : "";
80
+ const normalizedOverride = overrideUrl.trim();
81
+ if (normalizedOverride)
82
+ return normalizedOverride;
83
+ if (!normalizedIssuer)
84
+ return "";
85
+ try {
86
+ const response = await fetcher(`${normalizedIssuer}/.well-known/openid-configuration`);
87
+ if (response.ok) {
88
+ const discovery = await response.json();
89
+ if (discovery.token_endpoint)
90
+ return discovery.token_endpoint;
91
+ }
92
+ }
93
+ catch {
94
+ // 忽略 discovery 失败
95
+ }
96
+ return `${normalizedIssuer}/oauth/token`;
97
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @svadmin/sso — OIDC/OAuth2 SSO AuthProvider for svadmin.
3
+ *
4
+ * Uses the Authorization Code Flow with PKCE and supports rotation-safe
5
+ * refresh-token handling for browser applications.
6
+ */
7
+ import type { AuthProvider, Identity } from '@svadmin/core';
8
+ import { type AuthStateChangeCallback, type GetAccessTokenOptions, type RefreshLock, type SSOSession } from './session-manager.js';
9
+ export interface SSOConfig {
10
+ /** OIDC Issuer URL (e.g., 'https://your-tenant.okta.com') */
11
+ issuer: string;
12
+ /** OAuth2 Client ID */
13
+ clientId: string;
14
+ /** Redirect URI after login (e.g., '/callback') */
15
+ redirectUri: string;
16
+ /** OAuth2 scopes. Default: ['openid', 'profile', 'email'] */
17
+ scopes?: string[];
18
+ /** Where to redirect after logout */
19
+ postLogoutRedirectUri?: string;
20
+ /** Token storage backend. Default: 'local' (localStorage) */
21
+ storage?: 'local' | 'session' | TokenStorage;
22
+ /** Storage namespace. Defaults to an issuer/client-specific key. */
23
+ storageKey?: string;
24
+ /** Explicit legacy namespace to migrate after confirming it belongs to this provider. */
25
+ legacyStorageKey?: string;
26
+ /** Custom identity mapper — transform OIDC userinfo to svadmin Identity */
27
+ mapIdentity?: (userinfo: Record<string, unknown>) => Identity;
28
+ /** Auto-refresh tokens before expiry. Default: true */
29
+ autoRefresh?: boolean;
30
+ /** Seconds before expiry to trigger refresh. Default: 60 */
31
+ refreshBuffer?: number;
32
+ /** Additional authorization request parameters, e.g. audience or prompt. */
33
+ authorizationParams?: Record<string, string>;
34
+ /**
35
+ * Atomic authentication coordination lock override. Browsers use Web Locks
36
+ * by default and fail closed when unavailable; non-browser runtimes
37
+ * serialize within the process.
38
+ */
39
+ refreshLock?: RefreshLock;
40
+ /** Injectable fetch implementation for testing and SSR runtimes. */
41
+ fetcher?: typeof fetch;
42
+ /**
43
+ * Manual OAuth2 endpoints for providers that don't support OIDC discovery
44
+ * (e.g., GitHub). When provided, OIDC auto-discovery is skipped.
45
+ */
46
+ manualEndpoints?: {
47
+ authorization_endpoint: string;
48
+ token_endpoint: string;
49
+ userinfo_endpoint: string;
50
+ end_session_endpoint?: string;
51
+ };
52
+ }
53
+ export interface TokenStorage {
54
+ getItem: (key: string) => string | null;
55
+ setItem: (key: string, value: string) => void;
56
+ removeItem: (key: string) => void;
57
+ }
58
+ export interface SSOAuthProvider extends AuthProvider {
59
+ getSession: () => Promise<SSOSession | null>;
60
+ refreshSession: () => Promise<SSOSession | null>;
61
+ getAccessToken: (options?: GetAccessTokenOptions) => Promise<string | null>;
62
+ onAuthStateChange: (callback: AuthStateChangeCallback) => () => void;
63
+ createAuthenticatedFetch: (fetcher?: typeof fetch) => typeof fetch;
64
+ destroy: () => void;
65
+ }
66
+ /** Create an OIDC/OAuth2 SSO AuthProvider. */
67
+ export declare function createSSOAuthProvider(config: SSOConfig): SSOAuthProvider;
68
+ export type { AuthStateChangeCallback, AuthStateChangeEvent, GetAccessTokenOptions, RefreshLock, SSOSession, } from './session-manager.js';