@ovixa/auth-client 0.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.
- package/CHANGELOG.md +22 -0
- package/LICENSE +21 -0
- package/README.md +484 -0
- package/dist/chunk-UHRF6AFJ.js +544 -0
- package/dist/chunk-UHRF6AFJ.js.map +1 -0
- package/dist/chunk-Y5NJCTZO.js +143 -0
- package/dist/chunk-Y5NJCTZO.js.map +1 -0
- package/dist/index.d.ts +498 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/astro.d.ts +155 -0
- package/dist/middleware/astro.js +75 -0
- package/dist/middleware/astro.js.map +1 -0
- package/dist/middleware/express.d.ts +197 -0
- package/dist/middleware/express.js +87 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/types-Czfah64-.d.ts +57 -0
- package/package.json +78 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import {
|
|
2
|
+
OvixaAuthError
|
|
3
|
+
} from "./chunk-UHRF6AFJ.js";
|
|
4
|
+
|
|
5
|
+
// src/middleware/types.ts
|
|
6
|
+
var DEFAULT_COOKIE_OPTIONS = {
|
|
7
|
+
accessTokenCookie: "ovixa_access_token",
|
|
8
|
+
refreshTokenCookie: "ovixa_refresh_token",
|
|
9
|
+
path: "/",
|
|
10
|
+
secure: true,
|
|
11
|
+
httpOnly: true,
|
|
12
|
+
sameSite: "lax"
|
|
13
|
+
};
|
|
14
|
+
function resolveCookieOptions(options) {
|
|
15
|
+
return {
|
|
16
|
+
...DEFAULT_COOKIE_OPTIONS,
|
|
17
|
+
...options
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function isPublicRoute(path, publicRoutes) {
|
|
21
|
+
for (const route of publicRoutes) {
|
|
22
|
+
if (route.endsWith("/*")) {
|
|
23
|
+
const prefix = route.slice(0, -2);
|
|
24
|
+
if (path === prefix || path.startsWith(prefix + "/")) {
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
27
|
+
} else if (route === path) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// src/middleware/core.ts
|
|
35
|
+
async function checkAuth(auth, adapter, cookieOptions, autoRefresh = true) {
|
|
36
|
+
const options = resolveCookieOptions(cookieOptions);
|
|
37
|
+
const accessToken = adapter.getCookie(options.accessTokenCookie);
|
|
38
|
+
const refreshToken = adapter.getCookie(options.refreshTokenCookie);
|
|
39
|
+
if (!accessToken && !refreshToken) {
|
|
40
|
+
return {
|
|
41
|
+
context: createUnauthenticatedContext()
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (accessToken) {
|
|
45
|
+
try {
|
|
46
|
+
const result = await auth.verifyToken(accessToken);
|
|
47
|
+
const user = {
|
|
48
|
+
id: result.payload.sub,
|
|
49
|
+
email: result.payload.email,
|
|
50
|
+
emailVerified: result.payload.email_verified
|
|
51
|
+
};
|
|
52
|
+
const session = {
|
|
53
|
+
accessToken,
|
|
54
|
+
refreshToken: refreshToken || "",
|
|
55
|
+
expiresAt: new Date(result.payload.exp * 1e3)
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
context: {
|
|
59
|
+
user,
|
|
60
|
+
session,
|
|
61
|
+
isAuthenticated: true
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
if (error instanceof OvixaAuthError && error.code === "TOKEN_EXPIRED" && refreshToken && autoRefresh) {
|
|
66
|
+
return tryRefreshToken(auth, refreshToken);
|
|
67
|
+
}
|
|
68
|
+
if (refreshToken && autoRefresh) {
|
|
69
|
+
return tryRefreshToken(auth, refreshToken);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
context: createUnauthenticatedContext()
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (refreshToken && autoRefresh) {
|
|
77
|
+
return tryRefreshToken(auth, refreshToken);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
context: createUnauthenticatedContext()
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
async function tryRefreshToken(auth, refreshToken) {
|
|
84
|
+
try {
|
|
85
|
+
const tokens = await auth.refreshToken(refreshToken);
|
|
86
|
+
const authResult = await auth.toAuthResult(tokens);
|
|
87
|
+
return {
|
|
88
|
+
context: {
|
|
89
|
+
user: authResult.user,
|
|
90
|
+
session: authResult.session,
|
|
91
|
+
isAuthenticated: true
|
|
92
|
+
},
|
|
93
|
+
newTokens: tokens
|
|
94
|
+
};
|
|
95
|
+
} catch {
|
|
96
|
+
return {
|
|
97
|
+
context: createUnauthenticatedContext()
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
function createUnauthenticatedContext() {
|
|
102
|
+
return {
|
|
103
|
+
user: null,
|
|
104
|
+
session: null,
|
|
105
|
+
isAuthenticated: false
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function setAuthCookies(adapter, tokens, cookieOptions) {
|
|
109
|
+
const options = resolveCookieOptions(cookieOptions);
|
|
110
|
+
const baseOptions = {
|
|
111
|
+
path: options.path,
|
|
112
|
+
secure: options.secure,
|
|
113
|
+
httpOnly: options.httpOnly,
|
|
114
|
+
sameSite: options.sameSite,
|
|
115
|
+
domain: options.domain
|
|
116
|
+
};
|
|
117
|
+
adapter.setCookie(options.accessTokenCookie, tokens.access_token, {
|
|
118
|
+
...baseOptions,
|
|
119
|
+
maxAge: tokens.expires_in
|
|
120
|
+
});
|
|
121
|
+
const refreshMaxAge = options.maxAge ?? 30 * 24 * 60 * 60;
|
|
122
|
+
adapter.setCookie(options.refreshTokenCookie, tokens.refresh_token, {
|
|
123
|
+
...baseOptions,
|
|
124
|
+
maxAge: refreshMaxAge
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
function clearAuthCookies(adapter, cookieOptions) {
|
|
128
|
+
const options = resolveCookieOptions(cookieOptions);
|
|
129
|
+
const deleteOptions = {
|
|
130
|
+
path: options.path,
|
|
131
|
+
domain: options.domain
|
|
132
|
+
};
|
|
133
|
+
adapter.deleteCookie(options.accessTokenCookie, deleteOptions);
|
|
134
|
+
adapter.deleteCookie(options.refreshTokenCookie, deleteOptions);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export {
|
|
138
|
+
isPublicRoute,
|
|
139
|
+
checkAuth,
|
|
140
|
+
setAuthCookies,
|
|
141
|
+
clearAuthCookies
|
|
142
|
+
};
|
|
143
|
+
//# sourceMappingURL=chunk-Y5NJCTZO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/middleware/types.ts","../src/middleware/core.ts"],"sourcesContent":["/**\n * Middleware types for @ovixa/auth-client\n *\n * Provides generic adapter interfaces and shared types for framework integrations.\n */\n\nimport type { OvixaAuth, User, Session, TokenResponse } from '../index.js';\n\n/**\n * Cookie configuration options for auth middleware.\n */\nexport interface CookieOptions {\n /** Name of the access token cookie (default: 'ovixa_access_token') */\n accessTokenCookie?: string;\n /** Name of the refresh token cookie (default: 'ovixa_refresh_token') */\n refreshTokenCookie?: string;\n /** Cookie path (default: '/') */\n path?: string;\n /** Whether to use secure cookies (default: true) */\n secure?: boolean;\n /** Whether cookies are HTTP-only (default: true) */\n httpOnly?: boolean;\n /** SameSite cookie policy (default: 'lax') */\n sameSite?: 'strict' | 'lax' | 'none';\n /** Cookie domain (optional, defaults to current domain) */\n domain?: string;\n /** Max age for cookies in seconds (optional, defaults to token expiry) */\n maxAge?: number;\n}\n\n/**\n * Options for setting a cookie.\n */\nexport interface SetCookieOptions {\n path: string;\n secure: boolean;\n httpOnly: boolean;\n sameSite: 'strict' | 'lax' | 'none';\n domain?: string;\n maxAge?: number;\n expires?: Date;\n}\n\n/**\n * Options for deleting a cookie.\n */\nexport interface DeleteCookieOptions {\n path: string;\n domain?: string;\n}\n\n/**\n * Configuration for auth middleware.\n */\nexport interface AuthMiddlewareConfig {\n /** OvixaAuth client instance */\n auth: OvixaAuth;\n /** Cookie configuration options */\n cookies?: CookieOptions;\n /** Routes that don't require authentication */\n publicRoutes?: string[];\n /** URL to redirect unauthenticated requests (if not set, returns 401) */\n loginRedirect?: string;\n /** Whether to automatically refresh expired tokens (default: true) */\n autoRefresh?: boolean;\n}\n\n/**\n * Authentication context available in request handlers.\n */\nexport interface AuthContext {\n /** The authenticated user, or null if not authenticated */\n user: User | null;\n /** The current session, or null if not authenticated */\n session: Session | null;\n /** Whether the request is authenticated */\n isAuthenticated: boolean;\n}\n\n/**\n * Generic cookie adapter interface.\n *\n * Implement this interface to add support for additional frameworks.\n * The adapter abstracts cookie operations so the core auth logic\n * remains framework-agnostic.\n *\n * @example\n * ```typescript\n * class HonoCookieAdapter implements CookieAdapter {\n * constructor(private context: HonoContext) {}\n *\n * getCookie(name: string): string | undefined {\n * return this.context.get.cookie(name);\n * }\n *\n * setCookie(name: string, value: string, options: SetCookieOptions): void {\n * this.context.cookie(name, value, options);\n * }\n *\n * deleteCookie(name: string, options: DeleteCookieOptions): void {\n * this.context.cookie(name, '', { ...options, maxAge: 0 });\n * }\n * }\n * ```\n */\nexport interface CookieAdapter {\n /** Get a cookie value by name */\n getCookie(name: string): string | undefined;\n /** Set a cookie with the given value and options */\n setCookie(name: string, value: string, options: SetCookieOptions): void;\n /** Delete a cookie */\n deleteCookie(name: string, options: DeleteCookieOptions): void;\n}\n\n/**\n * Result of authentication check.\n */\nexport interface AuthCheckResult {\n /** Authentication context */\n context: AuthContext;\n /** New tokens if refresh occurred */\n newTokens?: TokenResponse;\n}\n\n/**\n * Default cookie configuration values.\n */\nexport const DEFAULT_COOKIE_OPTIONS: Required<Omit<CookieOptions, 'domain' | 'maxAge'>> = {\n accessTokenCookie: 'ovixa_access_token',\n refreshTokenCookie: 'ovixa_refresh_token',\n path: '/',\n secure: true,\n httpOnly: true,\n sameSite: 'lax',\n};\n\n/**\n * Resolve cookie options with defaults.\n */\nexport function resolveCookieOptions(\n options?: CookieOptions\n): Required<Omit<CookieOptions, 'domain' | 'maxAge'>> & Pick<CookieOptions, 'domain' | 'maxAge'> {\n return {\n ...DEFAULT_COOKIE_OPTIONS,\n ...options,\n };\n}\n\n/**\n * Check if a path matches any of the public routes.\n *\n * Supports:\n * - Exact matches: '/login'\n * - Wildcard suffix: '/api/public/*' matches '/api/public/foo/bar'\n *\n * @param path - The request path to check\n * @param publicRoutes - Array of public route patterns\n * @returns Whether the path is public\n */\nexport function isPublicRoute(path: string, publicRoutes: string[]): boolean {\n for (const route of publicRoutes) {\n if (route.endsWith('/*')) {\n // Wildcard match: '/api/*' matches '/api/foo'\n const prefix = route.slice(0, -2);\n if (path === prefix || path.startsWith(prefix + '/')) {\n return true;\n }\n } else if (route === path) {\n // Exact match\n return true;\n }\n }\n return false;\n}\n","/**\n * Core authentication logic for middleware.\n *\n * This module provides framework-agnostic authentication utilities\n * used by the Astro and Express middleware implementations.\n */\n\nimport type { OvixaAuth, TokenResponse, User, Session } from '../index.js';\nimport { OvixaAuthError } from '../index.js';\nimport type {\n CookieAdapter,\n AuthContext,\n AuthCheckResult,\n CookieOptions,\n SetCookieOptions,\n DeleteCookieOptions,\n} from './types.js';\nimport { resolveCookieOptions } from './types.js';\n\n/**\n * Check authentication status from cookies.\n *\n * This function:\n * 1. Reads the access token from cookies\n * 2. Attempts to verify the access token\n * 3. If expired and autoRefresh is enabled, attempts to refresh using the refresh token\n * 4. Returns the authentication context and any new tokens\n *\n * @param auth - OvixaAuth client instance\n * @param adapter - Cookie adapter for the framework\n * @param cookieOptions - Cookie configuration\n * @param autoRefresh - Whether to automatically refresh expired tokens\n * @returns Authentication check result with context and optional new tokens\n */\nexport async function checkAuth(\n auth: OvixaAuth,\n adapter: CookieAdapter,\n cookieOptions?: CookieOptions,\n autoRefresh = true\n): Promise<AuthCheckResult> {\n const options = resolveCookieOptions(cookieOptions);\n\n const accessToken = adapter.getCookie(options.accessTokenCookie);\n const refreshToken = adapter.getCookie(options.refreshTokenCookie);\n\n // No tokens at all\n if (!accessToken && !refreshToken) {\n return {\n context: createUnauthenticatedContext(),\n };\n }\n\n // Try to verify access token\n if (accessToken) {\n try {\n const result = await auth.verifyToken(accessToken);\n\n const user: User = {\n id: result.payload.sub,\n email: result.payload.email,\n emailVerified: result.payload.email_verified,\n };\n\n const session: Session = {\n accessToken,\n refreshToken: refreshToken || '',\n expiresAt: new Date(result.payload.exp * 1000),\n };\n\n return {\n context: {\n user,\n session,\n isAuthenticated: true,\n },\n };\n } catch (error) {\n // If token is expired and we have a refresh token, try to refresh\n if (\n error instanceof OvixaAuthError &&\n error.code === 'TOKEN_EXPIRED' &&\n refreshToken &&\n autoRefresh\n ) {\n return tryRefreshToken(auth, refreshToken);\n }\n\n // Token is invalid for another reason (bad signature, etc.)\n // Try refresh if we have a refresh token\n if (refreshToken && autoRefresh) {\n return tryRefreshToken(auth, refreshToken);\n }\n\n // No refresh token or refresh disabled, return unauthenticated\n return {\n context: createUnauthenticatedContext(),\n };\n }\n }\n\n // No access token but have refresh token - try to refresh\n if (refreshToken && autoRefresh) {\n return tryRefreshToken(auth, refreshToken);\n }\n\n return {\n context: createUnauthenticatedContext(),\n };\n}\n\n/**\n * Attempt to refresh tokens and return new authentication context.\n */\nasync function tryRefreshToken(auth: OvixaAuth, refreshToken: string): Promise<AuthCheckResult> {\n try {\n const tokens = await auth.refreshToken(refreshToken);\n const authResult = await auth.toAuthResult(tokens);\n\n return {\n context: {\n user: authResult.user,\n session: authResult.session,\n isAuthenticated: true,\n },\n newTokens: tokens,\n };\n } catch {\n // Refresh failed - return unauthenticated\n return {\n context: createUnauthenticatedContext(),\n };\n }\n}\n\n/**\n * Create an unauthenticated context.\n */\nfunction createUnauthenticatedContext(): AuthContext {\n return {\n user: null,\n session: null,\n isAuthenticated: false,\n };\n}\n\n/**\n * Set authentication cookies after login or token refresh.\n *\n * @param adapter - Cookie adapter for the framework\n * @param tokens - Token response from login/refresh\n * @param cookieOptions - Cookie configuration\n */\nexport function setAuthCookies(\n adapter: CookieAdapter,\n tokens: TokenResponse,\n cookieOptions?: CookieOptions\n): void {\n const options = resolveCookieOptions(cookieOptions);\n\n const baseOptions: SetCookieOptions = {\n path: options.path,\n secure: options.secure,\n httpOnly: options.httpOnly,\n sameSite: options.sameSite,\n domain: options.domain,\n };\n\n // Set access token cookie with expiry based on token\n adapter.setCookie(options.accessTokenCookie, tokens.access_token, {\n ...baseOptions,\n maxAge: tokens.expires_in,\n });\n\n // Set refresh token cookie with longer expiry (or custom maxAge)\n // Refresh tokens typically have longer lifetimes than access tokens\n // Default to 30 days if no maxAge specified\n const refreshMaxAge = options.maxAge ?? 30 * 24 * 60 * 60; // 30 days in seconds\n adapter.setCookie(options.refreshTokenCookie, tokens.refresh_token, {\n ...baseOptions,\n maxAge: refreshMaxAge,\n });\n}\n\n/**\n * Clear authentication cookies on logout.\n *\n * @param adapter - Cookie adapter for the framework\n * @param cookieOptions - Cookie configuration\n */\nexport function clearAuthCookies(adapter: CookieAdapter, cookieOptions?: CookieOptions): void {\n const options = resolveCookieOptions(cookieOptions);\n\n const deleteOptions: DeleteCookieOptions = {\n path: options.path,\n domain: options.domain,\n };\n\n adapter.deleteCookie(options.accessTokenCookie, deleteOptions);\n adapter.deleteCookie(options.refreshTokenCookie, deleteOptions);\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,498 @@
|
|
|
1
|
+
import { JWTPayload, JWTHeaderParameters } from 'jose';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @ovixa/auth-client
|
|
5
|
+
*
|
|
6
|
+
* Client SDK for Ovixa Auth service.
|
|
7
|
+
* Provides authentication, token verification, and session management.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Configuration options for the OvixaAuth client.
|
|
12
|
+
*/
|
|
13
|
+
interface AuthClientConfig {
|
|
14
|
+
/** The base URL of the Ovixa Auth service */
|
|
15
|
+
authUrl: string;
|
|
16
|
+
/** The realm ID to authenticate against */
|
|
17
|
+
realmId: string;
|
|
18
|
+
/** Your application's client secret (for server-side use only) */
|
|
19
|
+
clientSecret?: string;
|
|
20
|
+
/** Cache duration for JWKS in milliseconds (defaults to 1 hour) */
|
|
21
|
+
jwksCacheTtl?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* JWT claims for an Ovixa access token.
|
|
25
|
+
*/
|
|
26
|
+
interface AccessTokenPayload extends JWTPayload {
|
|
27
|
+
/** Subject (user ID) */
|
|
28
|
+
sub: string;
|
|
29
|
+
/** User's email address */
|
|
30
|
+
email: string;
|
|
31
|
+
/** Whether the user's email has been verified */
|
|
32
|
+
email_verified: boolean;
|
|
33
|
+
/** Issued at timestamp */
|
|
34
|
+
iat: number;
|
|
35
|
+
/** Expiration timestamp */
|
|
36
|
+
exp: number;
|
|
37
|
+
/** Issuer */
|
|
38
|
+
iss: string;
|
|
39
|
+
/** Audience (realm ID) */
|
|
40
|
+
aud: string;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Result of a successful token verification.
|
|
44
|
+
*/
|
|
45
|
+
interface VerifyResult {
|
|
46
|
+
/** The decoded token payload */
|
|
47
|
+
payload: AccessTokenPayload;
|
|
48
|
+
/** The protected header */
|
|
49
|
+
protectedHeader: JWTHeaderParameters;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Token response from the auth service.
|
|
53
|
+
*/
|
|
54
|
+
interface TokenResponse {
|
|
55
|
+
/** The access token (JWT) */
|
|
56
|
+
access_token: string;
|
|
57
|
+
/** The refresh token (JWT) */
|
|
58
|
+
refresh_token: string;
|
|
59
|
+
/** Token type (always "Bearer") */
|
|
60
|
+
token_type: 'Bearer';
|
|
61
|
+
/** Access token expiration time in seconds */
|
|
62
|
+
expires_in: number;
|
|
63
|
+
/** Whether this is a new user (only present in OAuth callback) */
|
|
64
|
+
is_new_user?: boolean;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* User information extracted from token payload.
|
|
68
|
+
*/
|
|
69
|
+
interface User {
|
|
70
|
+
/** User ID (UUID) */
|
|
71
|
+
id: string;
|
|
72
|
+
/** User's email address */
|
|
73
|
+
email: string;
|
|
74
|
+
/** Whether the email has been verified */
|
|
75
|
+
emailVerified: boolean;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Session information containing tokens and expiry.
|
|
79
|
+
*/
|
|
80
|
+
interface Session {
|
|
81
|
+
/** The access token (JWT) */
|
|
82
|
+
accessToken: string;
|
|
83
|
+
/** The refresh token for obtaining new access tokens */
|
|
84
|
+
refreshToken: string;
|
|
85
|
+
/** When the access token expires */
|
|
86
|
+
expiresAt: Date;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Combined authentication result with user and session data.
|
|
90
|
+
*/
|
|
91
|
+
interface AuthResult {
|
|
92
|
+
/** The authenticated user */
|
|
93
|
+
user: User;
|
|
94
|
+
/** The session tokens */
|
|
95
|
+
session: Session;
|
|
96
|
+
/** Whether this is a new user (only set for OAuth flows) */
|
|
97
|
+
isNewUser?: boolean;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Options for signup.
|
|
101
|
+
*/
|
|
102
|
+
interface SignupOptions {
|
|
103
|
+
/** User's email address */
|
|
104
|
+
email: string;
|
|
105
|
+
/** Password meeting requirements */
|
|
106
|
+
password: string;
|
|
107
|
+
/** Optional redirect URI after email verification */
|
|
108
|
+
redirectUri?: string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Response from signup endpoint.
|
|
112
|
+
*/
|
|
113
|
+
interface SignupResponse {
|
|
114
|
+
/** Whether the operation succeeded */
|
|
115
|
+
success: boolean;
|
|
116
|
+
/** Status message */
|
|
117
|
+
message: string;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Options for login.
|
|
121
|
+
*/
|
|
122
|
+
interface LoginOptions {
|
|
123
|
+
/** User's email address */
|
|
124
|
+
email: string;
|
|
125
|
+
/** User's password */
|
|
126
|
+
password: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Options for email verification.
|
|
130
|
+
*/
|
|
131
|
+
interface VerifyEmailOptions {
|
|
132
|
+
/** Verification token from email */
|
|
133
|
+
token: string;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Options for resending verification email.
|
|
137
|
+
*/
|
|
138
|
+
interface ResendVerificationOptions {
|
|
139
|
+
/** User's email address */
|
|
140
|
+
email: string;
|
|
141
|
+
/** Optional redirect URI after verification */
|
|
142
|
+
redirectUri?: string;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Generic success response.
|
|
146
|
+
*/
|
|
147
|
+
interface SuccessResponse {
|
|
148
|
+
/** Whether the operation succeeded */
|
|
149
|
+
success: boolean;
|
|
150
|
+
/** Optional status message */
|
|
151
|
+
message?: string;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Options for forgot password request.
|
|
155
|
+
*/
|
|
156
|
+
interface ForgotPasswordOptions {
|
|
157
|
+
/** User's email address */
|
|
158
|
+
email: string;
|
|
159
|
+
/** Optional redirect URI for password reset page */
|
|
160
|
+
redirectUri?: string;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Options for password reset.
|
|
164
|
+
*/
|
|
165
|
+
interface ResetPasswordOptions {
|
|
166
|
+
/** Reset token from email */
|
|
167
|
+
token: string;
|
|
168
|
+
/** New password */
|
|
169
|
+
password: string;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Supported OAuth providers.
|
|
173
|
+
*/
|
|
174
|
+
type OAuthProvider = 'google' | 'github';
|
|
175
|
+
/**
|
|
176
|
+
* Options for generating OAuth URL.
|
|
177
|
+
*/
|
|
178
|
+
interface GetOAuthUrlOptions {
|
|
179
|
+
/** OAuth provider */
|
|
180
|
+
provider: OAuthProvider;
|
|
181
|
+
/** Redirect URI after OAuth completes */
|
|
182
|
+
redirectUri: string;
|
|
183
|
+
}
|
|
184
|
+
/**
|
|
185
|
+
* Error thrown by OvixaAuth operations.
|
|
186
|
+
*/
|
|
187
|
+
declare class OvixaAuthError extends Error {
|
|
188
|
+
readonly code: string;
|
|
189
|
+
readonly statusCode?: number | undefined;
|
|
190
|
+
constructor(message: string, code: string, statusCode?: number | undefined);
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* OvixaAuth client for authenticating with the Ovixa Auth service.
|
|
194
|
+
*
|
|
195
|
+
* @example
|
|
196
|
+
* ```typescript
|
|
197
|
+
* const auth = new OvixaAuth({
|
|
198
|
+
* authUrl: 'https://auth.ovixa.io',
|
|
199
|
+
* realmId: 'your-realm-id',
|
|
200
|
+
* clientSecret: 'your-client-secret', // Optional, for server-side use
|
|
201
|
+
* });
|
|
202
|
+
*
|
|
203
|
+
* // Verify a token
|
|
204
|
+
* const result = await auth.verifyToken(accessToken);
|
|
205
|
+
* console.log(result.payload.email);
|
|
206
|
+
*
|
|
207
|
+
* // Refresh tokens
|
|
208
|
+
* const tokens = await auth.refreshToken(refreshToken);
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
declare class OvixaAuth {
|
|
212
|
+
private config;
|
|
213
|
+
private jwksCache;
|
|
214
|
+
constructor(config: AuthClientConfig);
|
|
215
|
+
/** Get the configured auth URL */
|
|
216
|
+
get authUrl(): string;
|
|
217
|
+
/** Get the configured realm ID */
|
|
218
|
+
get realmId(): string;
|
|
219
|
+
/**
|
|
220
|
+
* Get the JWKS URL for the auth service.
|
|
221
|
+
*/
|
|
222
|
+
get jwksUrl(): string;
|
|
223
|
+
/**
|
|
224
|
+
* Get the JWKS fetcher, creating a new one if necessary.
|
|
225
|
+
* The fetcher is cached based on the configured TTL.
|
|
226
|
+
*/
|
|
227
|
+
private getJwksFetcher;
|
|
228
|
+
/**
|
|
229
|
+
* Verify an access token and return the decoded payload.
|
|
230
|
+
*
|
|
231
|
+
* This method fetches the public key from the JWKS endpoint (with caching)
|
|
232
|
+
* and verifies the token's signature and claims.
|
|
233
|
+
*
|
|
234
|
+
* @param token - The access token (JWT) to verify
|
|
235
|
+
* @returns The verified token payload and header
|
|
236
|
+
* @throws {OvixaAuthError} If verification fails
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```typescript
|
|
240
|
+
* try {
|
|
241
|
+
* const result = await auth.verifyToken(accessToken);
|
|
242
|
+
* console.log('User ID:', result.payload.sub);
|
|
243
|
+
* console.log('Email:', result.payload.email);
|
|
244
|
+
* } catch (error) {
|
|
245
|
+
* if (error instanceof OvixaAuthError) {
|
|
246
|
+
* console.error('Token verification failed:', error.code);
|
|
247
|
+
* }
|
|
248
|
+
* }
|
|
249
|
+
* ```
|
|
250
|
+
*/
|
|
251
|
+
verifyToken(token: string): Promise<VerifyResult>;
|
|
252
|
+
/**
|
|
253
|
+
* Refresh an access token using a refresh token.
|
|
254
|
+
*
|
|
255
|
+
* This method exchanges a valid refresh token for a new access token
|
|
256
|
+
* and a new refresh token (token rotation).
|
|
257
|
+
*
|
|
258
|
+
* @param refreshToken - The refresh token to exchange
|
|
259
|
+
* @returns New token response with access and refresh tokens
|
|
260
|
+
* @throws {OvixaAuthError} If the refresh fails
|
|
261
|
+
*
|
|
262
|
+
* @example
|
|
263
|
+
* ```typescript
|
|
264
|
+
* try {
|
|
265
|
+
* const tokens = await auth.refreshToken(currentRefreshToken);
|
|
266
|
+
* // Store the new tokens
|
|
267
|
+
* saveTokens(tokens.access_token, tokens.refresh_token);
|
|
268
|
+
* } catch (error) {
|
|
269
|
+
* if (error instanceof OvixaAuthError) {
|
|
270
|
+
* // Refresh token is invalid or expired - user must re-authenticate
|
|
271
|
+
* redirectToLogin();
|
|
272
|
+
* }
|
|
273
|
+
* }
|
|
274
|
+
* ```
|
|
275
|
+
*/
|
|
276
|
+
refreshToken(refreshToken: string): Promise<TokenResponse>;
|
|
277
|
+
/**
|
|
278
|
+
* Invalidate the cached JWKS fetcher.
|
|
279
|
+
* Call this if you need to force a refresh of the public keys.
|
|
280
|
+
*/
|
|
281
|
+
clearJwksCache(): void;
|
|
282
|
+
/**
|
|
283
|
+
* Create a new user account.
|
|
284
|
+
*
|
|
285
|
+
* After signup, a verification email is sent. The user must verify their
|
|
286
|
+
* email before they can log in.
|
|
287
|
+
*
|
|
288
|
+
* @param options - Signup options
|
|
289
|
+
* @returns Signup response indicating success
|
|
290
|
+
* @throws {OvixaAuthError} If signup fails
|
|
291
|
+
*
|
|
292
|
+
* @example
|
|
293
|
+
* ```typescript
|
|
294
|
+
* try {
|
|
295
|
+
* await auth.signup({
|
|
296
|
+
* email: 'user@example.com',
|
|
297
|
+
* password: 'SecurePassword123!',
|
|
298
|
+
* redirectUri: 'https://myapp.com/verify-callback',
|
|
299
|
+
* });
|
|
300
|
+
* console.log('Verification email sent!');
|
|
301
|
+
* } catch (error) {
|
|
302
|
+
* if (error instanceof OvixaAuthError) {
|
|
303
|
+
* if (error.code === 'EMAIL_ALREADY_EXISTS') {
|
|
304
|
+
* console.error('Email is already registered');
|
|
305
|
+
* }
|
|
306
|
+
* }
|
|
307
|
+
* }
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
signup(options: SignupOptions): Promise<SignupResponse>;
|
|
311
|
+
/**
|
|
312
|
+
* Authenticate a user with email and password.
|
|
313
|
+
*
|
|
314
|
+
* @param options - Login options
|
|
315
|
+
* @returns Token response with access and refresh tokens
|
|
316
|
+
* @throws {OvixaAuthError} If login fails
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* ```typescript
|
|
320
|
+
* try {
|
|
321
|
+
* const tokens = await auth.login({
|
|
322
|
+
* email: 'user@example.com',
|
|
323
|
+
* password: 'SecurePassword123!',
|
|
324
|
+
* });
|
|
325
|
+
* console.log('Logged in!', tokens.access_token);
|
|
326
|
+
* } catch (error) {
|
|
327
|
+
* if (error instanceof OvixaAuthError) {
|
|
328
|
+
* if (error.code === 'EMAIL_NOT_VERIFIED') {
|
|
329
|
+
* console.error('Please verify your email first');
|
|
330
|
+
* } else if (error.code === 'INVALID_CREDENTIALS') {
|
|
331
|
+
* console.error('Invalid email or password');
|
|
332
|
+
* }
|
|
333
|
+
* }
|
|
334
|
+
* }
|
|
335
|
+
* ```
|
|
336
|
+
*/
|
|
337
|
+
login(options: LoginOptions): Promise<TokenResponse>;
|
|
338
|
+
/**
|
|
339
|
+
* Verify an email address using a verification token.
|
|
340
|
+
*
|
|
341
|
+
* This is the API flow that returns tokens. For browser redirect flow,
|
|
342
|
+
* use `GET /verify` directly.
|
|
343
|
+
*
|
|
344
|
+
* @param options - Verification options
|
|
345
|
+
* @returns Token response with access and refresh tokens
|
|
346
|
+
* @throws {OvixaAuthError} If verification fails
|
|
347
|
+
*
|
|
348
|
+
* @example
|
|
349
|
+
* ```typescript
|
|
350
|
+
* // Get token from URL query params after clicking email link
|
|
351
|
+
* const token = new URLSearchParams(window.location.search).get('token');
|
|
352
|
+
*
|
|
353
|
+
* try {
|
|
354
|
+
* const tokens = await auth.verifyEmail({ token });
|
|
355
|
+
* console.log('Email verified! Logged in.');
|
|
356
|
+
* } catch (error) {
|
|
357
|
+
* if (error instanceof OvixaAuthError && error.code === 'INVALID_TOKEN') {
|
|
358
|
+
* console.error('Invalid or expired verification link');
|
|
359
|
+
* }
|
|
360
|
+
* }
|
|
361
|
+
* ```
|
|
362
|
+
*/
|
|
363
|
+
verifyEmail(options: VerifyEmailOptions): Promise<TokenResponse>;
|
|
364
|
+
/**
|
|
365
|
+
* Resend a verification email for an unverified account.
|
|
366
|
+
*
|
|
367
|
+
* @param options - Resend options
|
|
368
|
+
* @returns Success response
|
|
369
|
+
* @throws {OvixaAuthError} If request fails
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```typescript
|
|
373
|
+
* await auth.resendVerification({
|
|
374
|
+
* email: 'user@example.com',
|
|
375
|
+
* redirectUri: 'https://myapp.com/verify-callback',
|
|
376
|
+
* });
|
|
377
|
+
* console.log('Verification email sent!');
|
|
378
|
+
* ```
|
|
379
|
+
*/
|
|
380
|
+
resendVerification(options: ResendVerificationOptions): Promise<SuccessResponse>;
|
|
381
|
+
/**
|
|
382
|
+
* Request a password reset email.
|
|
383
|
+
*
|
|
384
|
+
* Note: This endpoint always returns success to prevent email enumeration.
|
|
385
|
+
*
|
|
386
|
+
* @param options - Forgot password options
|
|
387
|
+
* @returns Success response
|
|
388
|
+
* @throws {OvixaAuthError} If request fails
|
|
389
|
+
*
|
|
390
|
+
* @example
|
|
391
|
+
* ```typescript
|
|
392
|
+
* await auth.forgotPassword({
|
|
393
|
+
* email: 'user@example.com',
|
|
394
|
+
* redirectUri: 'https://myapp.com/reset-password',
|
|
395
|
+
* });
|
|
396
|
+
* console.log('If the email exists, a reset link has been sent.');
|
|
397
|
+
* ```
|
|
398
|
+
*/
|
|
399
|
+
forgotPassword(options: ForgotPasswordOptions): Promise<SuccessResponse>;
|
|
400
|
+
/**
|
|
401
|
+
* Reset password using a reset token.
|
|
402
|
+
*
|
|
403
|
+
* @param options - Reset password options
|
|
404
|
+
* @returns Success response
|
|
405
|
+
* @throws {OvixaAuthError} If reset fails
|
|
406
|
+
*
|
|
407
|
+
* @example
|
|
408
|
+
* ```typescript
|
|
409
|
+
* // Get token from URL query params
|
|
410
|
+
* const token = new URLSearchParams(window.location.search).get('token');
|
|
411
|
+
*
|
|
412
|
+
* try {
|
|
413
|
+
* await auth.resetPassword({
|
|
414
|
+
* token,
|
|
415
|
+
* password: 'NewSecurePassword123!',
|
|
416
|
+
* });
|
|
417
|
+
* console.log('Password reset successfully!');
|
|
418
|
+
* } catch (error) {
|
|
419
|
+
* if (error instanceof OvixaAuthError) {
|
|
420
|
+
* if (error.code === 'INVALID_TOKEN') {
|
|
421
|
+
* console.error('Invalid or expired reset link');
|
|
422
|
+
* } else if (error.code === 'WEAK_PASSWORD') {
|
|
423
|
+
* console.error('Password does not meet requirements');
|
|
424
|
+
* }
|
|
425
|
+
* }
|
|
426
|
+
* }
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
resetPassword(options: ResetPasswordOptions): Promise<SuccessResponse>;
|
|
430
|
+
/**
|
|
431
|
+
* Revoke a refresh token (logout).
|
|
432
|
+
*
|
|
433
|
+
* @param refreshToken - The refresh token to revoke
|
|
434
|
+
* @returns Success response
|
|
435
|
+
* @throws {OvixaAuthError} If logout fails
|
|
436
|
+
*
|
|
437
|
+
* @example
|
|
438
|
+
* ```typescript
|
|
439
|
+
* await auth.logout(currentRefreshToken);
|
|
440
|
+
* // Clear local token storage
|
|
441
|
+
* localStorage.removeItem('refresh_token');
|
|
442
|
+
* localStorage.removeItem('access_token');
|
|
443
|
+
* ```
|
|
444
|
+
*/
|
|
445
|
+
logout(refreshToken: string): Promise<SuccessResponse>;
|
|
446
|
+
/**
|
|
447
|
+
* Generate an OAuth authorization URL.
|
|
448
|
+
*
|
|
449
|
+
* Redirect the user to this URL to start the OAuth flow. After authentication,
|
|
450
|
+
* the user will be redirected back to your `redirectUri` with tokens.
|
|
451
|
+
*
|
|
452
|
+
* @param options - OAuth URL options
|
|
453
|
+
* @returns The full OAuth authorization URL
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* ```typescript
|
|
457
|
+
* const googleAuthUrl = auth.getOAuthUrl({
|
|
458
|
+
* provider: 'google',
|
|
459
|
+
* redirectUri: 'https://myapp.com/auth/callback',
|
|
460
|
+
* });
|
|
461
|
+
*
|
|
462
|
+
* // Redirect user to start OAuth flow
|
|
463
|
+
* window.location.href = googleAuthUrl;
|
|
464
|
+
* ```
|
|
465
|
+
*/
|
|
466
|
+
getOAuthUrl(options: GetOAuthUrlOptions): string;
|
|
467
|
+
/**
|
|
468
|
+
* Transform a token response into an AuthResult with user and session data.
|
|
469
|
+
*
|
|
470
|
+
* This method decodes the access token to extract user information and
|
|
471
|
+
* creates a structured result object.
|
|
472
|
+
*
|
|
473
|
+
* @param tokenResponse - The token response from login, verify, or refresh
|
|
474
|
+
* @returns AuthResult with user and session data
|
|
475
|
+
* @throws {OvixaAuthError} If the access token cannot be decoded
|
|
476
|
+
*
|
|
477
|
+
* @example
|
|
478
|
+
* ```typescript
|
|
479
|
+
* const tokens = await auth.login({ email, password });
|
|
480
|
+
* const result = await auth.toAuthResult(tokens);
|
|
481
|
+
*
|
|
482
|
+
* console.log('User ID:', result.user.id);
|
|
483
|
+
* console.log('Email:', result.user.email);
|
|
484
|
+
* console.log('Expires at:', result.session.expiresAt);
|
|
485
|
+
* ```
|
|
486
|
+
*/
|
|
487
|
+
toAuthResult(tokenResponse: TokenResponse): Promise<AuthResult>;
|
|
488
|
+
/**
|
|
489
|
+
* Make an authenticated POST request to the auth service.
|
|
490
|
+
*/
|
|
491
|
+
private makeRequest;
|
|
492
|
+
/**
|
|
493
|
+
* Map HTTP status codes to error codes.
|
|
494
|
+
*/
|
|
495
|
+
private mapHttpStatusToErrorCode;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
export { type AccessTokenPayload, type AuthClientConfig, type AuthResult, type ForgotPasswordOptions, type GetOAuthUrlOptions, type LoginOptions, type OAuthProvider, OvixaAuth, OvixaAuthError, type ResendVerificationOptions, type ResetPasswordOptions, type Session, type SignupOptions, type SignupResponse, type SuccessResponse, type TokenResponse, type User, type VerifyEmailOptions, type VerifyResult };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|