astro-tokenkit 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Alex Mora
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,9 @@
1
+ import type { TokenBundle, FieldMapping } from '../types';
2
+ /**
3
+ * Auto-detect token fields from response body
4
+ */
5
+ export declare function autoDetectFields(body: any, fieldMapping?: FieldMapping): TokenBundle;
6
+ /**
7
+ * Parse JWT payload without verification (for reading only)
8
+ */
9
+ export declare function parseJWTPayload(token: string): Record<string, any> | null;
@@ -0,0 +1,123 @@
1
+ // packages/astro-tokenkit/src/auth/detector.ts
2
+ /**
3
+ * Common field names for access tokens
4
+ */
5
+ const ACCESS_TOKEN_FIELDS = [
6
+ 'access_token',
7
+ 'accessToken',
8
+ 'token',
9
+ 'jwt',
10
+ 'id_token',
11
+ 'idToken',
12
+ ];
13
+ /**
14
+ * Common field names for refresh tokens
15
+ */
16
+ const REFRESH_TOKEN_FIELDS = [
17
+ 'refresh_token',
18
+ 'refreshToken',
19
+ 'refresh',
20
+ ];
21
+ /**
22
+ * Common field names for expiration timestamp
23
+ */
24
+ const EXPIRES_AT_FIELDS = [
25
+ 'expires_at',
26
+ 'expiresAt',
27
+ 'exp',
28
+ 'expiry',
29
+ ];
30
+ /**
31
+ * Common field names for expires_in (seconds)
32
+ */
33
+ const EXPIRES_IN_FIELDS = [
34
+ 'expires_in',
35
+ 'expiresIn',
36
+ 'ttl',
37
+ ];
38
+ /**
39
+ * Common field names for session payload
40
+ */
41
+ const SESSION_PAYLOAD_FIELDS = [
42
+ 'user',
43
+ 'profile',
44
+ 'account',
45
+ 'data',
46
+ ];
47
+ /**
48
+ * Auto-detect token fields from response body
49
+ */
50
+ export function autoDetectFields(body, fieldMapping) {
51
+ // Helper to find field
52
+ const findField = (candidates, mapping) => {
53
+ if (mapping && body[mapping] !== undefined) {
54
+ return body[mapping];
55
+ }
56
+ for (const candidate of candidates) {
57
+ if (body[candidate] !== undefined) {
58
+ return body[candidate];
59
+ }
60
+ }
61
+ return undefined;
62
+ };
63
+ // Detect access token
64
+ const accessToken = findField(ACCESS_TOKEN_FIELDS, fieldMapping === null || fieldMapping === void 0 ? void 0 : fieldMapping.accessToken);
65
+ if (!accessToken) {
66
+ throw new Error(`Could not detect access token field. Tried: ${ACCESS_TOKEN_FIELDS.join(', ')}. ` +
67
+ `Provide custom parseLogin/parseRefresh or field mapping.`);
68
+ }
69
+ // Detect refresh token
70
+ const refreshToken = findField(REFRESH_TOKEN_FIELDS, fieldMapping === null || fieldMapping === void 0 ? void 0 : fieldMapping.refreshToken);
71
+ if (!refreshToken) {
72
+ throw new Error(`Could not detect refresh token field. Tried: ${REFRESH_TOKEN_FIELDS.join(', ')}. ` +
73
+ `Provide custom parseLogin/parseRefresh or field mapping.`);
74
+ }
75
+ // Detect expiration
76
+ let accessExpiresAt;
77
+ // Try expires_at first (timestamp)
78
+ const expiresAtValue = findField(EXPIRES_AT_FIELDS, fieldMapping === null || fieldMapping === void 0 ? void 0 : fieldMapping.expiresAt);
79
+ if (expiresAtValue !== undefined) {
80
+ accessExpiresAt = typeof expiresAtValue === 'number'
81
+ ? expiresAtValue
82
+ : parseInt(expiresAtValue, 10);
83
+ }
84
+ // Try expires_in (seconds from now)
85
+ if (accessExpiresAt === undefined) {
86
+ const expiresInValue = findField(EXPIRES_IN_FIELDS, fieldMapping === null || fieldMapping === void 0 ? void 0 : fieldMapping.expiresIn);
87
+ if (expiresInValue !== undefined) {
88
+ const expiresIn = typeof expiresInValue === 'number'
89
+ ? expiresInValue
90
+ : parseInt(expiresInValue, 10);
91
+ accessExpiresAt = Math.floor(Date.now() / 1000) + expiresIn;
92
+ }
93
+ }
94
+ if (accessExpiresAt === undefined) {
95
+ throw new Error(`Could not detect expiration field. Tried: ${[...EXPIRES_AT_FIELDS, ...EXPIRES_IN_FIELDS].join(', ')}. ` +
96
+ `Provide custom parseLogin/parseRefresh or field mapping.`);
97
+ }
98
+ // Detect session payload (optional)
99
+ const sessionPayload = findField(SESSION_PAYLOAD_FIELDS, fieldMapping === null || fieldMapping === void 0 ? void 0 : fieldMapping.sessionPayload);
100
+ return {
101
+ accessToken,
102
+ refreshToken,
103
+ accessExpiresAt,
104
+ sessionPayload: sessionPayload || undefined,
105
+ };
106
+ }
107
+ /**
108
+ * Parse JWT payload without verification (for reading only)
109
+ */
110
+ export function parseJWTPayload(token) {
111
+ try {
112
+ const parts = token.split('.');
113
+ if (parts.length !== 3) {
114
+ return null;
115
+ }
116
+ const payload = parts[1];
117
+ const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));
118
+ return JSON.parse(decoded);
119
+ }
120
+ catch (_a) {
121
+ return null;
122
+ }
123
+ }
@@ -0,0 +1,38 @@
1
+ import type { TokenBundle, Session, AuthConfig, TokenKitContext } from '../types';
2
+ /**
3
+ * Token Manager handles all token operations
4
+ */
5
+ export declare class TokenManager {
6
+ private config;
7
+ private singleFlight;
8
+ private baseURL;
9
+ constructor(config: AuthConfig, baseURL: string);
10
+ /**
11
+ * Perform login
12
+ */
13
+ login(ctx: TokenKitContext, credentials: any): Promise<TokenBundle>;
14
+ /**
15
+ * Perform token refresh
16
+ */
17
+ refresh(ctx: TokenKitContext, refreshToken: string): Promise<TokenBundle | null>;
18
+ /**
19
+ * Ensure valid tokens (with automatic refresh)
20
+ */
21
+ ensure(ctx: TokenKitContext): Promise<Session | null>;
22
+ /**
23
+ * Logout (clear tokens)
24
+ */
25
+ logout(ctx: TokenKitContext): Promise<void>;
26
+ /**
27
+ * Get current session (no refresh)
28
+ */
29
+ getSession(ctx: TokenKitContext): Session | null;
30
+ /**
31
+ * Check if authenticated
32
+ */
33
+ isAuthenticated(ctx: TokenKitContext): boolean;
34
+ /**
35
+ * Create flight key for single-flight deduplication
36
+ */
37
+ private createFlightKey;
38
+ }
@@ -0,0 +1,216 @@
1
+ // packages/astro-tokenkit/src/auth/manager.ts
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ import { autoDetectFields, parseJWTPayload } from './detector';
12
+ import { storeTokens, retrieveTokens, clearTokens } from './storage';
13
+ import { shouldRefresh, isExpired } from './policy';
14
+ /**
15
+ * Single-flight refresh manager
16
+ */
17
+ class SingleFlight {
18
+ constructor() {
19
+ this.inFlight = new Map();
20
+ }
21
+ execute(key, fn) {
22
+ return __awaiter(this, void 0, void 0, function* () {
23
+ const existing = this.inFlight.get(key);
24
+ if (existing)
25
+ return existing;
26
+ const promise = this.doExecute(key, fn);
27
+ this.inFlight.set(key, promise);
28
+ return promise;
29
+ });
30
+ }
31
+ doExecute(key, fn) {
32
+ return __awaiter(this, void 0, void 0, function* () {
33
+ try {
34
+ return yield fn();
35
+ }
36
+ finally {
37
+ this.inFlight.delete(key);
38
+ }
39
+ });
40
+ }
41
+ }
42
+ /**
43
+ * Token Manager handles all token operations
44
+ */
45
+ export class TokenManager {
46
+ constructor(config, baseURL) {
47
+ this.config = config;
48
+ this.singleFlight = new SingleFlight();
49
+ this.baseURL = baseURL;
50
+ }
51
+ /**
52
+ * Perform login
53
+ */
54
+ login(ctx, credentials) {
55
+ return __awaiter(this, void 0, void 0, function* () {
56
+ const url = this.baseURL + this.config.login;
57
+ const response = yield fetch(url, {
58
+ method: 'POST',
59
+ headers: { 'Content-Type': 'application/json' },
60
+ body: JSON.stringify(credentials),
61
+ });
62
+ if (!response.ok) {
63
+ throw new Error(`Login failed: ${response.status} ${response.statusText}`);
64
+ }
65
+ const body = yield response.json();
66
+ // Parse response
67
+ const bundle = this.config.parseLogin
68
+ ? this.config.parseLogin(body)
69
+ : autoDetectFields(body, this.config.fields);
70
+ // Store in cookies
71
+ storeTokens(ctx, bundle, this.config.cookies);
72
+ return bundle;
73
+ });
74
+ }
75
+ /**
76
+ * Perform token refresh
77
+ */
78
+ refresh(ctx, refreshToken) {
79
+ return __awaiter(this, void 0, void 0, function* () {
80
+ const url = this.baseURL + this.config.refresh;
81
+ try {
82
+ const response = yield fetch(url, {
83
+ method: 'POST',
84
+ headers: { 'Content-Type': 'application/json' },
85
+ body: JSON.stringify({ refreshToken }),
86
+ });
87
+ if (!response.ok) {
88
+ // 401/403 = invalid refresh token
89
+ if (response.status === 401 || response.status === 403) {
90
+ clearTokens(ctx, this.config.cookies);
91
+ return null;
92
+ }
93
+ throw new Error(`Refresh failed: ${response.status} ${response.statusText}`);
94
+ }
95
+ const body = yield response.json();
96
+ // Parse response
97
+ const bundle = this.config.parseRefresh
98
+ ? this.config.parseRefresh(body)
99
+ : autoDetectFields(body, this.config.fields);
100
+ // Validate bundle
101
+ if (!bundle.accessToken || !bundle.refreshToken || !bundle.accessExpiresAt) {
102
+ throw new Error('Invalid token bundle returned from refresh endpoint');
103
+ }
104
+ // Store new tokens
105
+ storeTokens(ctx, bundle, this.config.cookies);
106
+ return bundle;
107
+ }
108
+ catch (error) {
109
+ clearTokens(ctx, this.config.cookies);
110
+ throw error;
111
+ }
112
+ });
113
+ }
114
+ /**
115
+ * Ensure valid tokens (with automatic refresh)
116
+ */
117
+ ensure(ctx) {
118
+ return __awaiter(this, void 0, void 0, function* () {
119
+ var _a, _b, _c, _d, _e;
120
+ const now = Math.floor(Date.now() / 1000);
121
+ const tokens = retrieveTokens(ctx, this.config.cookies);
122
+ // No tokens
123
+ if (!tokens.accessToken || !tokens.refreshToken || !tokens.expiresAt) {
124
+ return null;
125
+ }
126
+ // Token expired
127
+ if (isExpired(tokens.expiresAt, now, this.config.policy)) {
128
+ const flightKey = this.createFlightKey(tokens.refreshToken);
129
+ const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken));
130
+ if (!bundle)
131
+ return null;
132
+ return {
133
+ accessToken: bundle.accessToken,
134
+ expiresAt: bundle.accessExpiresAt,
135
+ payload: (_b = (_a = bundle.sessionPayload) !== null && _a !== void 0 ? _a : parseJWTPayload(bundle.accessToken)) !== null && _b !== void 0 ? _b : undefined,
136
+ };
137
+ }
138
+ // Proactive refresh
139
+ if (shouldRefresh(tokens.expiresAt, now, tokens.lastRefreshAt, this.config.policy)) {
140
+ const flightKey = this.createFlightKey(tokens.refreshToken);
141
+ const bundle = yield this.singleFlight.execute(flightKey, () => this.refresh(ctx, tokens.refreshToken));
142
+ if (bundle) {
143
+ return {
144
+ accessToken: bundle.accessToken,
145
+ expiresAt: bundle.accessExpiresAt,
146
+ payload: (_d = (_c = bundle.sessionPayload) !== null && _c !== void 0 ? _c : parseJWTPayload(bundle.accessToken)) !== null && _d !== void 0 ? _d : undefined,
147
+ };
148
+ }
149
+ // Refresh failed, check if tokens still exist
150
+ const currentTokens = retrieveTokens(ctx, this.config.cookies);
151
+ if (!currentTokens.accessToken) {
152
+ return null;
153
+ }
154
+ }
155
+ // Return current session
156
+ return {
157
+ accessToken: tokens.accessToken,
158
+ expiresAt: tokens.expiresAt,
159
+ payload: (_e = parseJWTPayload(tokens.accessToken)) !== null && _e !== void 0 ? _e : undefined,
160
+ };
161
+ });
162
+ }
163
+ /**
164
+ * Logout (clear tokens)
165
+ */
166
+ logout(ctx) {
167
+ return __awaiter(this, void 0, void 0, function* () {
168
+ // Optionally call logout endpoint
169
+ if (this.config.logout) {
170
+ try {
171
+ const url = this.baseURL + this.config.logout;
172
+ yield fetch(url, { method: 'POST' });
173
+ }
174
+ catch (error) {
175
+ // Ignore logout endpoint errors
176
+ console.warn('Logout endpoint failed:', error);
177
+ }
178
+ }
179
+ clearTokens(ctx, this.config.cookies);
180
+ });
181
+ }
182
+ /**
183
+ * Get current session (no refresh)
184
+ */
185
+ getSession(ctx) {
186
+ var _a;
187
+ const tokens = retrieveTokens(ctx, this.config.cookies);
188
+ if (!tokens.accessToken || !tokens.expiresAt) {
189
+ return null;
190
+ }
191
+ return {
192
+ accessToken: tokens.accessToken,
193
+ expiresAt: tokens.expiresAt,
194
+ payload: (_a = parseJWTPayload(tokens.accessToken)) !== null && _a !== void 0 ? _a : undefined,
195
+ };
196
+ }
197
+ /**
198
+ * Check if authenticated
199
+ */
200
+ isAuthenticated(ctx) {
201
+ const tokens = retrieveTokens(ctx, this.config.cookies);
202
+ return !!(tokens.accessToken && tokens.refreshToken);
203
+ }
204
+ /**
205
+ * Create flight key for single-flight deduplication
206
+ */
207
+ createFlightKey(token) {
208
+ let hash = 0;
209
+ for (let i = 0; i < token.length; i++) {
210
+ const char = token.charCodeAt(i);
211
+ hash = ((hash << 5) - hash) + char;
212
+ hash = hash & hash;
213
+ }
214
+ return `flight_${Math.abs(hash).toString(36)}`;
215
+ }
216
+ }
@@ -0,0 +1,21 @@
1
+ import type { RefreshPolicy } from '../types';
2
+ /**
3
+ * Default refresh policy
4
+ */
5
+ export declare const DEFAULT_POLICY: {
6
+ refreshBefore: number;
7
+ clockSkew: number;
8
+ minInterval: number;
9
+ };
10
+ /**
11
+ * Normalize refresh policy (convert time strings to seconds)
12
+ */
13
+ export declare function normalizePolicy(policy?: RefreshPolicy): Required<RefreshPolicy>;
14
+ /**
15
+ * Check if token should be refreshed
16
+ */
17
+ export declare function shouldRefresh(expiresAt: number, now: number, lastRefreshAt: number | null, policy?: RefreshPolicy): boolean;
18
+ /**
19
+ * Check if token is expired
20
+ */
21
+ export declare function isExpired(expiresAt: number, now: number, policy?: RefreshPolicy): boolean;
@@ -0,0 +1,66 @@
1
+ // packages/astro-tokenkit/src/auth/policy.ts
2
+ import { parseTime } from '../utils/time';
3
+ /**
4
+ * Default refresh policy
5
+ */
6
+ export const DEFAULT_POLICY = {
7
+ refreshBefore: 300, // 5 minutes
8
+ clockSkew: 60, // 1 minute
9
+ minInterval: 30, // 30 seconds
10
+ };
11
+ /**
12
+ * Normalize refresh policy (convert time strings to seconds)
13
+ */
14
+ export function normalizePolicy(policy = {}) {
15
+ return {
16
+ refreshBefore: policy.refreshBefore
17
+ ? parseTime(policy.refreshBefore)
18
+ : DEFAULT_POLICY.refreshBefore,
19
+ clockSkew: policy.clockSkew
20
+ ? parseTime(policy.clockSkew)
21
+ : DEFAULT_POLICY.clockSkew,
22
+ minInterval: policy.minInterval
23
+ ? parseTime(policy.minInterval)
24
+ : DEFAULT_POLICY.minInterval,
25
+ };
26
+ }
27
+ /**
28
+ * Check if token should be refreshed
29
+ */
30
+ export function shouldRefresh(expiresAt, now, lastRefreshAt, policy = {}) {
31
+ const normalized = normalizePolicy(policy);
32
+ const refreshBefore = typeof normalized.refreshBefore === 'number'
33
+ ? normalized.refreshBefore
34
+ : parseTime(normalized.refreshBefore);
35
+ const clockSkew = typeof normalized.clockSkew === 'number'
36
+ ? normalized.clockSkew
37
+ : parseTime(normalized.clockSkew);
38
+ const minInterval = typeof normalized.minInterval === 'number'
39
+ ? normalized.minInterval
40
+ : parseTime(normalized.minInterval);
41
+ // Adjust for clock skew
42
+ const adjustedNow = now + clockSkew;
43
+ // Check if near expiration
44
+ const timeUntilExpiry = expiresAt - adjustedNow;
45
+ if (timeUntilExpiry > refreshBefore) {
46
+ return false;
47
+ }
48
+ // Check minimum interval
49
+ if (lastRefreshAt !== null) {
50
+ const timeSinceLastRefresh = now - lastRefreshAt;
51
+ if (timeSinceLastRefresh < minInterval) {
52
+ return false;
53
+ }
54
+ }
55
+ return true;
56
+ }
57
+ /**
58
+ * Check if token is expired
59
+ */
60
+ export function isExpired(expiresAt, now, policy = {}) {
61
+ const normalized = normalizePolicy(policy);
62
+ const clockSkew = typeof normalized.clockSkew === 'number'
63
+ ? normalized.clockSkew
64
+ : parseTime(normalized.clockSkew);
65
+ return now > expiresAt + clockSkew;
66
+ }
@@ -0,0 +1,40 @@
1
+ import type { TokenBundle, CookieConfig, TokenKitContext } from '../types';
2
+ /**
3
+ * Cookie names
4
+ */
5
+ export interface CookieNames {
6
+ accessToken: string;
7
+ refreshToken: string;
8
+ expiresAt: string;
9
+ lastRefreshAt: string;
10
+ }
11
+ /**
12
+ * Get cookie names with optional prefix
13
+ */
14
+ export declare function getCookieNames(prefix?: string): CookieNames;
15
+ /**
16
+ * Get cookie options with smart defaults
17
+ */
18
+ export declare function getCookieOptions(config?: CookieConfig): {
19
+ secure: boolean;
20
+ sameSite: "strict" | "lax" | "none";
21
+ httpOnly: boolean;
22
+ domain: string | undefined;
23
+ };
24
+ /**
25
+ * Store token bundle in cookies
26
+ */
27
+ export declare function storeTokens(ctx: TokenKitContext, bundle: TokenBundle, cookieConfig?: CookieConfig): void;
28
+ /**
29
+ * Retrieve tokens from cookies
30
+ */
31
+ export declare function retrieveTokens(ctx: TokenKitContext, cookieConfig?: CookieConfig): {
32
+ accessToken: string | null;
33
+ refreshToken: string | null;
34
+ expiresAt: number | null;
35
+ lastRefreshAt: number | null;
36
+ };
37
+ /**
38
+ * Clear all auth cookies
39
+ */
40
+ export declare function clearTokens(ctx: TokenKitContext, cookieConfig?: CookieConfig): void;
@@ -0,0 +1,72 @@
1
+ // packages/astro-tokenkit/src/auth/storage.ts
2
+ /**
3
+ * Get cookie names with optional prefix
4
+ */
5
+ export function getCookieNames(prefix) {
6
+ const p = prefix ? `${prefix}_` : '';
7
+ return {
8
+ accessToken: `${p}access_token`,
9
+ refreshToken: `${p}refresh_token`,
10
+ expiresAt: `${p}access_expires_at`,
11
+ lastRefreshAt: `${p}last_refresh_at`,
12
+ };
13
+ }
14
+ /**
15
+ * Get cookie options with smart defaults
16
+ */
17
+ export function getCookieOptions(config = {}) {
18
+ var _a, _b;
19
+ const isProduction = process.env.NODE_ENV === 'production';
20
+ return {
21
+ secure: (_a = config.secure) !== null && _a !== void 0 ? _a : isProduction,
22
+ sameSite: (_b = config.sameSite) !== null && _b !== void 0 ? _b : 'lax',
23
+ httpOnly: true, // Always HttpOnly for security
24
+ domain: config.domain,
25
+ };
26
+ }
27
+ /**
28
+ * Store token bundle in cookies
29
+ */
30
+ export function storeTokens(ctx, bundle, cookieConfig = {}) {
31
+ const names = getCookieNames(cookieConfig.prefix);
32
+ const options = getCookieOptions(cookieConfig);
33
+ const now = Math.floor(Date.now() / 1000);
34
+ // Calculate max age
35
+ const accessMaxAge = Math.max(0, bundle.accessExpiresAt - now);
36
+ const refreshMaxAge = bundle.refreshExpiresAt
37
+ ? Math.max(0, bundle.refreshExpiresAt - now)
38
+ : 7 * 24 * 60 * 60; // Default 7 days
39
+ // Set access token
40
+ ctx.cookies.set(names.accessToken, bundle.accessToken, Object.assign(Object.assign({}, options), { maxAge: accessMaxAge, path: '/' }));
41
+ // Set refresh token (restricted path for security)
42
+ ctx.cookies.set(names.refreshToken, bundle.refreshToken, Object.assign(Object.assign({}, options), { maxAge: refreshMaxAge, path: '/' }));
43
+ // Set expiration timestamp
44
+ ctx.cookies.set(names.expiresAt, bundle.accessExpiresAt.toString(), Object.assign(Object.assign({}, options), { maxAge: accessMaxAge, path: '/' }));
45
+ // Set last refresh timestamp
46
+ ctx.cookies.set(names.lastRefreshAt, now.toString(), Object.assign(Object.assign({}, options), { maxAge: accessMaxAge, path: '/' }));
47
+ }
48
+ /**
49
+ * Retrieve tokens from cookies
50
+ */
51
+ export function retrieveTokens(ctx, cookieConfig = {}) {
52
+ var _a, _b, _c, _d;
53
+ const names = getCookieNames(cookieConfig.prefix);
54
+ const accessToken = ((_a = ctx.cookies.get(names.accessToken)) === null || _a === void 0 ? void 0 : _a.value) || null;
55
+ const refreshToken = ((_b = ctx.cookies.get(names.refreshToken)) === null || _b === void 0 ? void 0 : _b.value) || null;
56
+ const expiresAtStr = (_c = ctx.cookies.get(names.expiresAt)) === null || _c === void 0 ? void 0 : _c.value;
57
+ const expiresAt = expiresAtStr ? parseInt(expiresAtStr, 10) : null;
58
+ const lastRefreshAtStr = (_d = ctx.cookies.get(names.lastRefreshAt)) === null || _d === void 0 ? void 0 : _d.value;
59
+ const lastRefreshAt = lastRefreshAtStr ? parseInt(lastRefreshAtStr, 10) : null;
60
+ return { accessToken, refreshToken, expiresAt, lastRefreshAt };
61
+ }
62
+ /**
63
+ * Clear all auth cookies
64
+ */
65
+ export function clearTokens(ctx, cookieConfig = {}) {
66
+ const names = getCookieNames(cookieConfig.prefix);
67
+ const options = getCookieOptions(cookieConfig);
68
+ ctx.cookies.delete(names.accessToken, Object.assign(Object.assign({}, options), { path: '/' }));
69
+ ctx.cookies.delete(names.refreshToken, Object.assign(Object.assign({}, options), { path: '/' }));
70
+ ctx.cookies.delete(names.expiresAt, Object.assign(Object.assign({}, options), { path: '/' }));
71
+ ctx.cookies.delete(names.lastRefreshAt, Object.assign(Object.assign({}, options), { path: '/' }));
72
+ }
@@ -0,0 +1,72 @@
1
+ import type { ClientConfig, RequestConfig, RequestOptions, Session, TokenKitContext } from '../types';
2
+ import { TokenManager } from '../auth/manager';
3
+ import { type ContextOptions } from './context';
4
+ /**
5
+ * API Client
6
+ */
7
+ export declare class APIClient {
8
+ tokenManager?: TokenManager;
9
+ private config;
10
+ contextOptions: ContextOptions;
11
+ constructor(config: ClientConfig);
12
+ /**
13
+ * GET request
14
+ */
15
+ get<T = any>(url: string, options?: RequestOptions): Promise<T>;
16
+ /**
17
+ * POST request
18
+ */
19
+ post<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T>;
20
+ /**
21
+ * PUT request
22
+ */
23
+ put<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T>;
24
+ /**
25
+ * PATCH request
26
+ */
27
+ patch<T = any>(url: string, data?: any, options?: RequestOptions): Promise<T>;
28
+ /**
29
+ * DELETE request
30
+ */
31
+ delete<T = any>(url: string, options?: RequestOptions): Promise<T>;
32
+ /**
33
+ * Generic request method
34
+ */
35
+ request<T = any>(config: RequestConfig): Promise<T>;
36
+ /**
37
+ * Execute single request
38
+ */
39
+ private executeRequest;
40
+ /**
41
+ * Parse response
42
+ */
43
+ private parseResponse;
44
+ /**
45
+ * Build full URL with query params
46
+ */
47
+ private buildURL;
48
+ /**
49
+ * Build request headers
50
+ */
51
+ private buildHeaders;
52
+ /**
53
+ * Login
54
+ */
55
+ login(credentials: any, ctx?: TokenKitContext): Promise<void>;
56
+ /**
57
+ * Logout
58
+ */
59
+ logout(ctx?: TokenKitContext): Promise<void>;
60
+ /**
61
+ * Check if authenticated
62
+ */
63
+ isAuthenticated(ctx?: TokenKitContext): boolean;
64
+ /**
65
+ * Get current session
66
+ */
67
+ getSession(ctx?: TokenKitContext): Session | null;
68
+ }
69
+ /**
70
+ * Create API client
71
+ */
72
+ export declare function createClient(config: ClientConfig): APIClient;