@svadmin/sso 0.10.0 → 0.11.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/dist/errors.js ADDED
@@ -0,0 +1,86 @@
1
+ const RETRYABLE_STATUS_CODES = new Set([0, 408, 429, 500, 502, 503, 504]);
2
+ const TERMINAL_OAUTH_CODES = new Set([
3
+ 'invalid_grant',
4
+ 'invalid_token',
5
+ 'session_not_found',
6
+ 'refresh_token_not_found',
7
+ 'refresh_token_already_used',
8
+ 'refresh_token_reuse_detected',
9
+ 'invalid_refresh_token',
10
+ 'session_expired',
11
+ 'session_persistence_failed',
12
+ 'auth_retry_exhausted',
13
+ 'session_refresh_failed',
14
+ ]);
15
+ export class SSOAuthError extends Error {
16
+ statusCode;
17
+ code;
18
+ details;
19
+ body;
20
+ retryable;
21
+ cause;
22
+ constructor(message, statusCode, options = {}) {
23
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
24
+ this.name = 'SSOAuthError';
25
+ this.statusCode = statusCode;
26
+ this.code = options.code;
27
+ this.details = options.details;
28
+ this.body = options.body;
29
+ this.retryable = options.retryable ?? RETRYABLE_STATUS_CODES.has(statusCode);
30
+ this.cause = options.cause;
31
+ }
32
+ }
33
+ function asRecord(value) {
34
+ return typeof value === 'object' && value !== null
35
+ ? value
36
+ : null;
37
+ }
38
+ function firstString(...values) {
39
+ return values.find((value) => typeof value === 'string');
40
+ }
41
+ async function readResponseBody(response) {
42
+ const text = await response.text();
43
+ if (!text)
44
+ return undefined;
45
+ try {
46
+ return JSON.parse(text);
47
+ }
48
+ catch {
49
+ return text;
50
+ }
51
+ }
52
+ export async function createSSOAuthResponseError(response, fallbackMessage) {
53
+ const body = await readResponseBody(response);
54
+ const record = asRecord(body);
55
+ const nestedError = asRecord(record?.error);
56
+ const code = firstString(record?.code, record?.error_code, nestedError?.code, nestedError?.error_code, record?.error);
57
+ const description = firstString(record?.error_description, nestedError?.error_description);
58
+ const responseMessage = firstString(record?.message, nestedError?.message);
59
+ const textMessage = typeof body === 'string' ? body : undefined;
60
+ const statusMessage = response.statusText || `HTTP ${response.status}`;
61
+ return new SSOAuthError(description ?? responseMessage ?? textMessage ?? `${fallbackMessage}: ${statusMessage}`, response.status, {
62
+ code,
63
+ details: record?.details ?? nestedError?.details,
64
+ body,
65
+ });
66
+ }
67
+ export function createSSOAuthNetworkError(error, fallbackMessage) {
68
+ if (error instanceof SSOAuthError)
69
+ return error;
70
+ const isAbort = (typeof DOMException !== 'undefined'
71
+ && error instanceof DOMException
72
+ && error.name === 'AbortError') || (typeof error === 'object'
73
+ && error !== null
74
+ && 'name' in error
75
+ && error.name === 'AbortError');
76
+ return new SSOAuthError(error instanceof Error ? error.message : fallbackMessage, 0, {
77
+ code: isAbort ? 'request_aborted' : 'network_error',
78
+ retryable: !isAbort,
79
+ cause: error,
80
+ });
81
+ }
82
+ export function isTerminalSessionError(error) {
83
+ const record = asRecord(error);
84
+ const code = firstString(record?.code, record?.error_code, asRecord(record?.error)?.code, asRecord(record?.error)?.error_code);
85
+ return code !== undefined && TERMINAL_OAUTH_CODES.has(code);
86
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @svadmin/sso — OIDC/OAuth2 SSO AuthProvider plugin
3
+ *
4
+ * Usage:
5
+ * ```ts
6
+ * import { createSSOAuthProvider, createGoogleAuth } from '@svadmin/sso';
7
+ * ```
8
+ */
9
+ export { createSSOAuthProvider } from './auth-provider.js';
10
+ export type { AuthStateChangeCallback, AuthStateChangeEvent, GetAccessTokenOptions, RefreshLock, SSOAuthProvider, SSOConfig, SSOSession, TokenStorage, } from './auth-provider.js';
11
+ export { SSOAuthError } from './errors.js';
12
+ export { createGoogleAuth, createMicrosoftAuth, createGitHubAuth, createGitLabAuth, createKeycloakAuth, createAuth0Auth, createSupauthAuth, } from './presets.js';
13
+ export type { PresetOptions } from './presets.js';
14
+ export { generateState, generateVerifier, generateChallenge, isValidReturnTo, getForwardedOrigin, resolveAuthorizeUrl, resolveTokenUrl, } from './auth-helpers.js';
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * @svadmin/sso — OIDC/OAuth2 SSO AuthProvider plugin
3
+ *
4
+ * Usage:
5
+ * ```ts
6
+ * import { createSSOAuthProvider, createGoogleAuth } from '@svadmin/sso';
7
+ * ```
8
+ */
9
+ export { createSSOAuthProvider } from './auth-provider.js';
10
+ export { SSOAuthError } from './errors.js';
11
+ // Social login presets
12
+ export { createGoogleAuth, createMicrosoftAuth, createGitHubAuth, createGitLabAuth, createKeycloakAuth, createAuth0Auth, createSupauthAuth, } from './presets.js';
13
+ // Auth helpers (PKCE, OIDC discovery, redirect validation)
14
+ export { generateState, generateVerifier, generateChallenge, isValidReturnTo, getForwardedOrigin, resolveAuthorizeUrl, resolveTokenUrl, } from './auth-helpers.js';
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Social login presets — pre-configured SSO providers for common identity platforms.
3
+ *
4
+ * Each factory function returns an AuthProvider via `createSSOAuthProvider()`
5
+ * with the correct issuer and default scopes for that platform.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { createGoogleAuth } from '@svadmin/sso';
10
+ * import { setAuthProvider } from '@svadmin/core';
11
+ *
12
+ * setAuthProvider(createGoogleAuth('your-client-id'));
13
+ * ```
14
+ */
15
+ import { type SSOConfig } from './auth-provider.js';
16
+ type PresetOptions = Omit<SSOConfig, 'issuer' | 'clientId'>;
17
+ /**
18
+ * Google Sign-In via OIDC (OpenID Connect).
19
+ *
20
+ * Prerequisites:
21
+ * 1. Create OAuth2 credentials at https://console.cloud.google.com/apis/credentials
22
+ * 2. Add authorized redirect URIs (e.g., `http://localhost:5173/callback`)
23
+ *
24
+ * @param clientId - Google OAuth2 Client ID
25
+ */
26
+ export declare function createGoogleAuth(clientId: string, opts?: PresetOptions): import("./auth-provider.js").SSOAuthProvider;
27
+ /**
28
+ * Microsoft / Azure AD Sign-In via OIDC.
29
+ *
30
+ * @param clientId - Azure App Registration Client ID
31
+ * @param tenantId - Azure tenant ID. Default: 'common' (multi-tenant)
32
+ */
33
+ export declare function createMicrosoftAuth(clientId: string, tenantId?: string, opts?: PresetOptions): import("./auth-provider.js").SSOAuthProvider;
34
+ /**
35
+ * GitHub Sign-In via OAuth2.
36
+ *
37
+ * Note: GitHub does NOT support OIDC discovery, so we manually specify endpoints.
38
+ * The `issuer` field is used for identification only.
39
+ *
40
+ * Prerequisites:
41
+ * 1. Create an OAuth App at https://github.com/settings/developers
42
+ * 2. Set the callback URL to your redirect URI
43
+ *
44
+ * @param clientId - GitHub OAuth App Client ID
45
+ */
46
+ export declare function createGitHubAuth(clientId: string, opts?: PresetOptions): import("./auth-provider.js").SSOAuthProvider;
47
+ /**
48
+ * GitLab Sign-In via OIDC.
49
+ *
50
+ * @param clientId - GitLab Application ID
51
+ * @param instanceUrl - GitLab instance URL. Default: 'https://gitlab.com'
52
+ */
53
+ export declare function createGitLabAuth(clientId: string, instanceUrl?: string, opts?: PresetOptions): import("./auth-provider.js").SSOAuthProvider;
54
+ /**
55
+ * Keycloak Sign-In via OIDC.
56
+ *
57
+ * @param baseUrl - Keycloak base URL (e.g., 'https://keycloak.example.com')
58
+ * @param realm - Keycloak realm name
59
+ * @param clientId - Keycloak Client ID
60
+ */
61
+ export declare function createKeycloakAuth(baseUrl: string, realm: string, clientId: string, opts?: PresetOptions): import("./auth-provider.js").SSOAuthProvider;
62
+ /**
63
+ * Auth0 Sign-In via OIDC.
64
+ *
65
+ * @param domain - Auth0 domain (e.g., 'your-tenant.auth0.com')
66
+ * @param clientId - Auth0 Client ID
67
+ */
68
+ export declare function createAuth0Auth(domain: string, clientId: string, opts?: PresetOptions): import("./auth-provider.js").SSOAuthProvider;
69
+ export type { PresetOptions };
70
+ /**
71
+ * Supauth OIDC Sign-In (GoTrue-compatible identity provider).
72
+ *
73
+ * Compatible with SupaCloud, SupaOAuth, and any GoTrue-based auth backend.
74
+ * Uses standard OIDC Authorization Code Flow with PKCE.
75
+ *
76
+ * @param issuer - Supauth issuer URL (e.g., 'https://auth.example.com')
77
+ * @param clientId - OAuth2 Client ID
78
+ */
79
+ export declare function createSupauthAuth(clientId: string, opts?: PresetOptions & {
80
+ issuer?: string;
81
+ }): import("./auth-provider.js").SSOAuthProvider;
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Social login presets — pre-configured SSO providers for common identity platforms.
3
+ *
4
+ * Each factory function returns an AuthProvider via `createSSOAuthProvider()`
5
+ * with the correct issuer and default scopes for that platform.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { createGoogleAuth } from '@svadmin/sso';
10
+ * import { setAuthProvider } from '@svadmin/core';
11
+ *
12
+ * setAuthProvider(createGoogleAuth('your-client-id'));
13
+ * ```
14
+ */
15
+ import { createSSOAuthProvider } from './auth-provider.js';
16
+ function resolveRedirectUri(opts) {
17
+ return opts?.redirectUri ?? (typeof window !== 'undefined' ? `${window.location.origin}/callback` : '/callback');
18
+ }
19
+ // ─── Google ──────────────────────────────────────────────────
20
+ /**
21
+ * Google Sign-In via OIDC (OpenID Connect).
22
+ *
23
+ * Prerequisites:
24
+ * 1. Create OAuth2 credentials at https://console.cloud.google.com/apis/credentials
25
+ * 2. Add authorized redirect URIs (e.g., `http://localhost:5173/callback`)
26
+ *
27
+ * @param clientId - Google OAuth2 Client ID
28
+ */
29
+ export function createGoogleAuth(clientId, opts) {
30
+ const issuer = 'https://accounts.google.com';
31
+ return createSSOAuthProvider({
32
+ issuer,
33
+ clientId,
34
+ scopes: ['openid', 'profile', 'email'],
35
+ redirectUri: resolveRedirectUri(opts),
36
+ ...opts,
37
+ });
38
+ }
39
+ // ─── Microsoft / Azure AD (Entra ID) ────────────────────────
40
+ /**
41
+ * Microsoft / Azure AD Sign-In via OIDC.
42
+ *
43
+ * @param clientId - Azure App Registration Client ID
44
+ * @param tenantId - Azure tenant ID. Default: 'common' (multi-tenant)
45
+ */
46
+ export function createMicrosoftAuth(clientId, tenantId = 'common', opts) {
47
+ const issuer = `https://login.microsoftonline.com/${tenantId}/v2.0`;
48
+ return createSSOAuthProvider({
49
+ issuer,
50
+ clientId,
51
+ scopes: ['openid', 'profile', 'email'],
52
+ redirectUri: resolveRedirectUri(opts),
53
+ ...opts,
54
+ });
55
+ }
56
+ // ─── GitHub ──────────────────────────────────────────────────
57
+ /**
58
+ * GitHub Sign-In via OAuth2.
59
+ *
60
+ * Note: GitHub does NOT support OIDC discovery, so we manually specify endpoints.
61
+ * The `issuer` field is used for identification only.
62
+ *
63
+ * Prerequisites:
64
+ * 1. Create an OAuth App at https://github.com/settings/developers
65
+ * 2. Set the callback URL to your redirect URI
66
+ *
67
+ * @param clientId - GitHub OAuth App Client ID
68
+ */
69
+ export function createGitHubAuth(clientId, opts) {
70
+ // GitHub uses standard OAuth2, not OIDC — requires manual endpoint config.
71
+ // We leverage the SSO provider but override the discovery with manual endpoints.
72
+ const redirectUri = resolveRedirectUri(opts);
73
+ const issuer = 'https://github.com';
74
+ return createSSOAuthProvider({
75
+ issuer,
76
+ clientId,
77
+ scopes: ['read:user', 'user:email'],
78
+ redirectUri,
79
+ // GitHub doesn't support OIDC discovery, provide manual endpoints
80
+ manualEndpoints: {
81
+ authorization_endpoint: 'https://github.com/login/oauth/authorize',
82
+ token_endpoint: 'https://github.com/login/oauth/access_token',
83
+ userinfo_endpoint: 'https://api.github.com/user',
84
+ },
85
+ mapIdentity: (userinfo) => ({
86
+ id: String(userinfo.id ?? ''),
87
+ name: userinfo.name ?? userinfo.login ?? '',
88
+ avatar: userinfo.avatar_url ?? undefined,
89
+ email: userinfo.email ?? undefined,
90
+ }),
91
+ ...opts,
92
+ });
93
+ }
94
+ // ─── GitLab ──────────────────────────────────────────────────
95
+ /**
96
+ * GitLab Sign-In via OIDC.
97
+ *
98
+ * @param clientId - GitLab Application ID
99
+ * @param instanceUrl - GitLab instance URL. Default: 'https://gitlab.com'
100
+ */
101
+ export function createGitLabAuth(clientId, instanceUrl = 'https://gitlab.com', opts) {
102
+ const issuer = instanceUrl;
103
+ return createSSOAuthProvider({
104
+ issuer,
105
+ clientId,
106
+ scopes: ['openid', 'profile', 'email'],
107
+ redirectUri: resolveRedirectUri(opts),
108
+ ...opts,
109
+ });
110
+ }
111
+ // ─── Keycloak ────────────────────────────────────────────────
112
+ /**
113
+ * Keycloak Sign-In via OIDC.
114
+ *
115
+ * @param baseUrl - Keycloak base URL (e.g., 'https://keycloak.example.com')
116
+ * @param realm - Keycloak realm name
117
+ * @param clientId - Keycloak Client ID
118
+ */
119
+ export function createKeycloakAuth(baseUrl, realm, clientId, opts) {
120
+ const issuer = `${baseUrl.replace(/\/$/, '')}/realms/${realm}`;
121
+ return createSSOAuthProvider({
122
+ issuer,
123
+ clientId,
124
+ scopes: ['openid', 'profile', 'email'],
125
+ redirectUri: resolveRedirectUri(opts),
126
+ ...opts,
127
+ });
128
+ }
129
+ // ─── Auth0 ───────────────────────────────────────────────────
130
+ /**
131
+ * Auth0 Sign-In via OIDC.
132
+ *
133
+ * @param domain - Auth0 domain (e.g., 'your-tenant.auth0.com')
134
+ * @param clientId - Auth0 Client ID
135
+ */
136
+ export function createAuth0Auth(domain, clientId, opts) {
137
+ const issuer = `https://${domain.replace(/^https?:\/\//, '').replace(/\/$/, '')}`;
138
+ return createSSOAuthProvider({
139
+ issuer,
140
+ clientId,
141
+ scopes: ['openid', 'profile', 'email'],
142
+ redirectUri: resolveRedirectUri(opts),
143
+ ...opts,
144
+ });
145
+ }
146
+ // ─── Supauth / GoTrue ────────────────────────────────────────
147
+ /**
148
+ * Supauth OIDC Sign-In (GoTrue-compatible identity provider).
149
+ *
150
+ * Compatible with SupaCloud, SupaOAuth, and any GoTrue-based auth backend.
151
+ * Uses standard OIDC Authorization Code Flow with PKCE.
152
+ *
153
+ * @param issuer - Supauth issuer URL (e.g., 'https://auth.example.com')
154
+ * @param clientId - OAuth2 Client ID
155
+ */
156
+ export function createSupauthAuth(clientId, opts) {
157
+ const issuer = opts?.issuer ?? '';
158
+ return createSSOAuthProvider({
159
+ issuer,
160
+ clientId,
161
+ scopes: ['openid', 'profile', 'email'],
162
+ redirectUri: resolveRedirectUri(opts),
163
+ ...opts,
164
+ });
165
+ }
@@ -0,0 +1,51 @@
1
+ export interface SSOSession {
2
+ access_token: string;
3
+ id_token?: string;
4
+ refresh_token?: string;
5
+ expires_at?: number;
6
+ token_type: string;
7
+ }
8
+ export interface GetAccessTokenOptions {
9
+ minValiditySeconds?: number;
10
+ forceRefresh?: boolean;
11
+ }
12
+ export type AuthStateChangeEvent = 'TOKEN_REFRESHED' | 'SIGNED_OUT';
13
+ export type AuthStateChangeCallback = (event: AuthStateChangeEvent, session: SSOSession | null) => void | Promise<void>;
14
+ export interface RefreshLock {
15
+ /** Serialize every context that shares the same token storage. */
16
+ request<T>(name: string, operation: () => Promise<T>): Promise<T>;
17
+ }
18
+ interface SessionManagerOptions {
19
+ storage: TokenStorageLike;
20
+ storageKey: string;
21
+ autoRefresh: boolean;
22
+ refreshBuffer: number;
23
+ refreshLock?: RefreshLock;
24
+ refresh: (session: SSOSession) => Promise<SSOSession>;
25
+ legacyStorageKey?: string;
26
+ }
27
+ export interface TokenStorageLike {
28
+ getItem: (key: string) => string | null;
29
+ setItem: (key: string, value: string) => void;
30
+ removeItem: (key: string) => void;
31
+ }
32
+ export interface SessionManager {
33
+ getSession: () => SSOSession | null;
34
+ clearInvalidSession: () => Promise<void>;
35
+ getAuthGeneration: () => number;
36
+ saveSession: (session: SSOSession, event?: AuthStateChangeEvent) => void;
37
+ clearSession: () => void;
38
+ beginAuthAttempt: () => void;
39
+ beginSignOut: () => void;
40
+ setLoginState: (verifier: string, state: string) => void;
41
+ getPKCEVerifier: () => string | null;
42
+ getLoginState: () => string | null;
43
+ clearLoginState: (expectedState?: string) => void;
44
+ runAuthMutation: <T>(operation: () => Promise<T>) => Promise<T>;
45
+ refreshSession: () => Promise<SSOSession | null>;
46
+ getAccessToken: (options?: GetAccessTokenOptions) => Promise<string | null>;
47
+ onAuthStateChange: (callback: AuthStateChangeCallback) => () => void;
48
+ destroy: () => void;
49
+ }
50
+ export declare function createSessionManager(options: SessionManagerOptions): SessionManager;
51
+ export {};