@isaias_pv/custos-sdk 1.0.0 → 1.2.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/dist/Custos.d.ts CHANGED
@@ -4,20 +4,24 @@ export declare class Custos {
4
4
  private storage;
5
5
  private api;
6
6
  private listeners;
7
- private tokenIssuedAt;
7
+ private tokenExpiryTimer;
8
8
  constructor(config: CustosConfig);
9
- login(): Promise<void>;
9
+ login(additionalParams?: Record<string, string>): Promise<void>;
10
10
  logout(): Promise<void>;
11
11
  handleCallback(): Promise<void>;
12
12
  getUser(): User | null;
13
13
  getAccessToken(): string | null;
14
+ getRefreshToken(): string | null;
14
15
  isAuthenticated(): boolean;
15
16
  getState(): AuthState;
16
- private setupTokenRefresh;
17
- private shouldRefreshToken;
17
+ validateToken(): Promise<boolean>;
18
+ private setupTokenExpiryMonitoring;
19
+ private clearTokenExpiryTimer;
18
20
  refreshToken(): Promise<void>;
19
21
  on(event: AuthEventType, callback: (event: AuthEvent) => void): void;
20
22
  off(event: AuthEventType, callback: (event: AuthEvent) => void): void;
21
23
  private emit;
24
+ clearStorage(): void;
25
+ destroy(): void;
22
26
  }
23
27
  //# sourceMappingURL=Custos.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Custos.d.ts","sourceRoot":"","sources":["../src/Custos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAKlF,qBAAa,MAAM;IAClB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,SAAS,CAAsD;IACvE,OAAO,CAAC,aAAa,CAAuB;gBAEhC,MAAM,EAAE,YAAY;IAqB1B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAetB,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAevB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAuCrC,OAAO,IAAI,IAAI,GAAG,IAAI;IAItB,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B,eAAe,IAAI,OAAO;IAI1B,QAAQ,IAAI,SAAS;IASrB,OAAO,CAAC,iBAAiB;IAQzB,OAAO,CAAC,kBAAkB;IAOpB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BnC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAOpE,GAAG,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAIrE,OAAO,CAAC,IAAI;CAIZ"}
1
+ {"version":3,"file":"Custos.d.ts","sourceRoot":"","sources":["../src/Custos.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAWlF,qBAAa,MAAM;IAClB,OAAO,CAAC,MAAM,CAAyB;IACvC,OAAO,CAAC,OAAO,CAAU;IACzB,OAAO,CAAC,GAAG,CAAY;IACvB,OAAO,CAAC,SAAS,CAAsD;IACvE,OAAO,CAAC,gBAAgB,CAAa;gBAEzB,MAAM,EAAE,YAAY;IA8B1B,KAAK,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IA6B/D,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAgBvB,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IAiErC,OAAO,IAAI,IAAI,GAAG,IAAI;IAItB,cAAc,IAAI,MAAM,GAAG,IAAI;IAI/B,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,eAAe,IAAI,OAAO;IAI1B,QAAQ,IAAI,SAAS;IAQf,aAAa,IAAI,OAAO,CAAC,OAAO,CAAC;IAavC,OAAO,CAAC,0BAA0B;IAuBlC,OAAO,CAAC,qBAAqB;IAOvB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBnC,EAAE,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAOpE,GAAG,CAAC,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,GAAG,IAAI;IAIrE,OAAO,CAAC,IAAI;IAOZ,YAAY,IAAI,IAAI;IAIpB,OAAO,IAAI,IAAI;CAIf"}
package/dist/Custos.js ADDED
@@ -0,0 +1,210 @@
1
+ import { Storage } from './storage';
2
+ import { ApiClient } from './api';
3
+ import { generateState, parseQueryString, generateCodeVerifier, generateCodeChallenge, normalizeScope } from './utils';
4
+ export class Custos {
5
+ constructor(config) {
6
+ this.tokenExpiryTimer = null;
7
+ // Normalize scope
8
+ const scope = normalizeScope(config.scope);
9
+ this.config = {
10
+ clientId: config.clientId,
11
+ clientSecret: config.clientSecret || '',
12
+ redirectUri: config.redirectUri,
13
+ apiUrl: config.apiUrl || 'https://custos.alimzen.com',
14
+ scope,
15
+ responseType: config.responseType || 'code',
16
+ state: config.state || generateState(),
17
+ usePKCE: config.usePKCE !== false, // Default to true
18
+ codeChallengeMethod: config.codeChallengeMethod || 'S256',
19
+ grantType: config.grantType || 'authorization_code',
20
+ };
21
+ this.storage = new Storage();
22
+ this.api = new ApiClient(this.config.apiUrl);
23
+ this.listeners = new Map();
24
+ // Handle callback automatically
25
+ if (typeof window !== 'undefined') {
26
+ this.handleCallback();
27
+ this.setupTokenExpiryMonitoring();
28
+ }
29
+ }
30
+ // ==================== Authentication Methods ====================
31
+ async login(additionalParams) {
32
+ const state = this.config.state;
33
+ this.storage.setState('oauth_state', state);
34
+ const params = {
35
+ response_type: this.config.responseType,
36
+ client_id: this.config.clientId,
37
+ redirect_uri: this.config.redirectUri,
38
+ scope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,
39
+ state,
40
+ ...additionalParams,
41
+ };
42
+ // Add PKCE if enabled
43
+ if (this.config.usePKCE) {
44
+ const codeVerifier = generateCodeVerifier();
45
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
46
+ this.storage.setCodeVerifier(codeVerifier);
47
+ this.storage.setCodeChallenge(codeChallenge);
48
+ params.code_challenge = codeChallenge;
49
+ params.code_challenge_method = this.config.codeChallengeMethod;
50
+ }
51
+ const authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;
52
+ window.location.href = authUrl;
53
+ }
54
+ async logout() {
55
+ const tokens = this.storage.getTokens();
56
+ if (tokens?.accessToken) {
57
+ try {
58
+ await this.api.logout(tokens.accessToken);
59
+ }
60
+ catch (error) {
61
+ console.error('Logout error:', error);
62
+ }
63
+ }
64
+ this.clearTokenExpiryTimer();
65
+ this.storage.clear();
66
+ this.emit('logout', null);
67
+ }
68
+ async handleCallback() {
69
+ const params = parseQueryString(window.location.href);
70
+ // Check for errors
71
+ const error = params.error;
72
+ if (error) {
73
+ const errorDescription = params.error_description || error;
74
+ this.emit('error', { error, error_description: errorDescription });
75
+ throw new Error(errorDescription);
76
+ }
77
+ // Check for authorization code
78
+ const code = params.code;
79
+ if (!code)
80
+ return;
81
+ // Validate state
82
+ const state = params.state;
83
+ const savedState = this.storage.getState('oauth_state');
84
+ if (state !== savedState) {
85
+ this.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });
86
+ throw new Error('Invalid state parameter');
87
+ }
88
+ this.storage.removeState('oauth_state');
89
+ try {
90
+ // Get code_verifier if using PKCE
91
+ const codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;
92
+ // Exchange code for tokens
93
+ const tokens = await this.api.exchangeCodeForTokens(code, this.config.clientId, this.config.redirectUri, codeVerifier, this.config.clientSecret);
94
+ this.storage.setTokens(tokens);
95
+ // Get user info
96
+ const user = await this.api.getUserInfo(tokens.accessToken);
97
+ this.storage.setUser(user);
98
+ // Clean up PKCE data
99
+ if (this.config.usePKCE) {
100
+ this.storage.removeCodeVerifier();
101
+ this.storage.removeCodeChallenge();
102
+ }
103
+ // Setup token expiry monitoring
104
+ this.setupTokenExpiryMonitoring();
105
+ this.emit('login', { user, tokens });
106
+ // Clean URL (remove query params)
107
+ window.history.replaceState({}, document.title, window.location.pathname + window.location.hash);
108
+ }
109
+ catch (error) {
110
+ this.emit('error', error);
111
+ throw error;
112
+ }
113
+ }
114
+ // ==================== User Methods ====================
115
+ getUser() {
116
+ return this.storage.getUser();
117
+ }
118
+ getAccessToken() {
119
+ return this.storage.getTokens()?.accessToken || null;
120
+ }
121
+ getRefreshToken() {
122
+ return this.storage.getTokens()?.refreshToken || null;
123
+ }
124
+ isAuthenticated() {
125
+ return this.storage.hasValidToken() && !!this.storage.getUser();
126
+ }
127
+ getState() {
128
+ return {
129
+ isAuthenticated: this.isAuthenticated(),
130
+ user: this.getUser(),
131
+ tokens: this.storage.getTokens(),
132
+ };
133
+ }
134
+ async validateToken() {
135
+ const accessToken = this.getAccessToken();
136
+ if (!accessToken)
137
+ return false;
138
+ try {
139
+ return await this.api.validateToken(accessToken);
140
+ }
141
+ catch {
142
+ return false;
143
+ }
144
+ }
145
+ // ==================== Token Refresh ====================
146
+ setupTokenExpiryMonitoring() {
147
+ this.clearTokenExpiryTimer();
148
+ const tokens = this.storage.getTokens();
149
+ const issuedAt = this.storage.getTokenIssuedAt();
150
+ if (!tokens || !issuedAt)
151
+ return;
152
+ // Refresh 5 minutes before expiry
153
+ const timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer
154
+ if (timeUntilRefresh > 0) {
155
+ this.tokenExpiryTimer = setTimeout(async () => {
156
+ try {
157
+ await this.refreshToken();
158
+ }
159
+ catch (error) {
160
+ this.emit('token-expired', error);
161
+ await this.logout();
162
+ }
163
+ }, timeUntilRefresh);
164
+ }
165
+ }
166
+ clearTokenExpiryTimer() {
167
+ if (this.tokenExpiryTimer) {
168
+ clearTimeout(this.tokenExpiryTimer);
169
+ this.tokenExpiryTimer = null;
170
+ }
171
+ }
172
+ async refreshToken() {
173
+ const tokens = this.storage.getTokens();
174
+ if (!tokens?.refreshToken) {
175
+ throw new Error('No refresh token available');
176
+ }
177
+ try {
178
+ const newTokens = await this.api.refreshAccessToken(tokens.refreshToken, this.config.clientId, this.config.clientSecret);
179
+ this.storage.setTokens(newTokens);
180
+ this.setupTokenExpiryMonitoring();
181
+ this.emit('token-refresh', newTokens);
182
+ }
183
+ catch (error) {
184
+ this.emit('error', error);
185
+ throw error;
186
+ }
187
+ }
188
+ // ==================== Event Handling ====================
189
+ on(event, callback) {
190
+ if (!this.listeners.has(event)) {
191
+ this.listeners.set(event, new Set());
192
+ }
193
+ this.listeners.get(event).add(callback);
194
+ }
195
+ off(event, callback) {
196
+ this.listeners.get(event)?.delete(callback);
197
+ }
198
+ emit(type, data) {
199
+ const event = { type, data };
200
+ this.listeners.get(type)?.forEach((callback) => callback(event));
201
+ }
202
+ // ==================== Utility Methods ====================
203
+ clearStorage() {
204
+ this.storage.clear();
205
+ }
206
+ destroy() {
207
+ this.clearTokenExpiryTimer();
208
+ this.listeners.clear();
209
+ }
210
+ }
package/dist/api.d.ts CHANGED
@@ -2,9 +2,10 @@ import { AuthTokens, User } from './types';
2
2
  export declare class ApiClient {
3
3
  private baseUrl;
4
4
  constructor(baseUrl: string);
5
- exchangeCodeForTokens(code: string, clientId: string, redirectUri: string, clientSecret?: string): Promise<AuthTokens>;
5
+ exchangeCodeForTokens(code: string, clientId: string, redirectUri: string, codeVerifier?: string, clientSecret?: string): Promise<AuthTokens>;
6
6
  getUserInfo(accessToken: string): Promise<User>;
7
7
  refreshAccessToken(refreshToken: string, clientId: string, clientSecret?: string): Promise<AuthTokens>;
8
8
  logout(accessToken: string): Promise<void>;
9
+ validateToken(accessToken: string): Promise<boolean>;
9
10
  }
10
11
  //# sourceMappingURL=api.d.ts.map
package/dist/api.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE3C,qBAAa,SAAS;IACrB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAIrB,qBAAqB,CAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,UAAU,CAAC;IA4BhB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAc/C,kBAAkB,CACvB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,UAAU,CAAC;IA2BhB,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAQhD"}
1
+ {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAE3C,qBAAa,SAAS;IACrB,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,EAAE,MAAM;IAIrB,qBAAqB,CAC1B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EACnB,YAAY,CAAC,EAAE,MAAM,EACrB,YAAY,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,UAAU,CAAC;IA8ChB,WAAW,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAe/C,kBAAkB,CACvB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,EAChB,YAAY,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,UAAU,CAAC;IAkChB,MAAM,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAY1D"}
package/dist/api.js ADDED
@@ -0,0 +1,104 @@
1
+ export class ApiClient {
2
+ constructor(baseUrl) {
3
+ this.baseUrl = baseUrl;
4
+ }
5
+ async exchangeCodeForTokens(code, clientId, redirectUri, codeVerifier, clientSecret) {
6
+ const body = {
7
+ grant_type: 'authorization_code',
8
+ code,
9
+ client_id: clientId,
10
+ redirect_uri: redirectUri,
11
+ };
12
+ // Add PKCE code_verifier if present
13
+ if (codeVerifier) {
14
+ body.code_verifier = codeVerifier;
15
+ }
16
+ // Add client_secret if present (for confidential clients)
17
+ if (clientSecret) {
18
+ body.client_secret = clientSecret;
19
+ }
20
+ const response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {
21
+ method: 'POST',
22
+ headers: {
23
+ 'Content-Type': 'application/x-www-form-urlencoded',
24
+ },
25
+ body: new URLSearchParams(body).toString(),
26
+ });
27
+ if (!response.ok) {
28
+ const errorData = await response.json().catch(() => ({
29
+ error: 'unknown_error',
30
+ error_description: 'Failed to exchange code for tokens'
31
+ }));
32
+ throw new Error(errorData.error_description || errorData.error || 'Token exchange failed');
33
+ }
34
+ const result = await response.json();
35
+ const data = result.data || result; // Support both {data: {...}} and direct response
36
+ return {
37
+ accessToken: data.access_token,
38
+ refreshToken: data.refresh_token,
39
+ expiresIn: data.expires_in,
40
+ tokenType: data.token_type || 'Bearer',
41
+ };
42
+ }
43
+ async getUserInfo(accessToken) {
44
+ const response = await fetch(`${this.baseUrl}/api/v1/auth/userinfo`, {
45
+ headers: {
46
+ Authorization: `Bearer ${accessToken}`,
47
+ },
48
+ });
49
+ if (!response.ok) {
50
+ throw new Error('Failed to get user info');
51
+ }
52
+ const result = await response.json();
53
+ return result.data || result;
54
+ }
55
+ async refreshAccessToken(refreshToken, clientId, clientSecret) {
56
+ const body = {
57
+ grant_type: 'refresh_token',
58
+ refresh_token: refreshToken,
59
+ client_id: clientId,
60
+ };
61
+ if (clientSecret) {
62
+ body.client_secret = clientSecret;
63
+ }
64
+ const response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {
65
+ method: 'POST',
66
+ headers: {
67
+ 'Content-Type': 'application/x-www-form-urlencoded',
68
+ },
69
+ body: new URLSearchParams(body).toString(),
70
+ });
71
+ if (!response.ok) {
72
+ throw new Error('Failed to refresh token');
73
+ }
74
+ const result = await response.json();
75
+ const data = result.data || result;
76
+ return {
77
+ accessToken: data.access_token,
78
+ refreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided
79
+ expiresIn: data.expires_in,
80
+ tokenType: data.token_type || 'Bearer',
81
+ };
82
+ }
83
+ async logout(accessToken) {
84
+ await fetch(`${this.baseUrl}/api/v1/auth/revoke`, {
85
+ method: 'POST',
86
+ headers: {
87
+ Authorization: `Bearer ${accessToken}`,
88
+ },
89
+ });
90
+ }
91
+ async validateToken(accessToken) {
92
+ try {
93
+ const response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {
94
+ headers: {
95
+ Authorization: `Bearer ${accessToken}`,
96
+ },
97
+ });
98
+ return response.ok;
99
+ }
100
+ catch {
101
+ return false;
102
+ }
103
+ }
104
+ }
package/dist/index.cjs.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e="custos_";class t{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(t){this.storage.setItem(`${e}tokens`,JSON.stringify(t))}getTokens(){const t=this.storage.getItem(`${e}tokens`);return t?JSON.parse(t):null}setUser(t){this.storage.setItem(`${e}user`,JSON.stringify(t))}getUser(){const t=this.storage.getItem(`${e}user`);return t?JSON.parse(t):null}clear(){this.storage.removeItem(`${e}tokens`),this.storage.removeItem(`${e}user`)}setState(t,s){this.storage.setItem(`${e}${t}`,s)}getState(t){return this.storage.getItem(`${e}${t}`)}removeState(t){this.storage.removeItem(`${e}${t}`)}}class s{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,s,o){const r=await fetch(`${this.baseUrl}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"authorization_code",code:e,client_id:t,client_secret:o,redirect_uri:s})});if(!r.ok)throw new Error("Failed to exchange code for tokens");const n=await r.json();return{accessToken:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,tokenType:n.token_type}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/oauth/userinfo`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");return t.json()}async refreshAccessToken(e,t,s){const o=await fetch(`${this.baseUrl}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"refresh_token",refresh_token:e,client_id:t,client_secret:s})});if(!o.ok)throw new Error("Failed to refresh token");const r=await o.json();return{accessToken:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,tokenType:r.token_type}}async logout(e){await fetch(`${this.baseUrl}/oauth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}}exports.Custos=class{constructor(e){this.tokenIssuedAt=null,this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:e.scope||["openid","profile","email"]},this.storage=new t,this.api=new s(this.config.apiUrl),this.listeners=new Map,this.handleCallback(),this.setupTokenRefresh()}async login(){const e=function(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}();this.storage.setState("oauth_state",e);const t=new URLSearchParams({response_type:"code",client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:this.config.scope.join(" "),state:e});window.location.href=`${this.config.apiUrl}/oauth/authorize?${t}`}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,s)=>{t[s]=e}),t}(window.location.href),t=e.code,s=e.state;if(!t)return;if(s!==this.storage.getState("oauth_state"))throw new Error("Invalid state parameter");this.storage.removeState("oauth_state");try{const e=await this.api.exchangeCodeForTokens(t,this.config.clientId,this.config.redirectUri,this.config.clientSecret);this.tokenIssuedAt=Date.now(),this.storage.setTokens(e);const s=await this.api.getUserInfo(e.accessToken);this.storage.setUser(s),this.emit("login",{user:s,tokens:e}),window.history.replaceState({},document.title,window.location.pathname)}catch(e){throw this.emit("error",e),e}}getUser(){return this.storage.getUser()}getAccessToken(){return this.storage.getTokens()?.accessToken||null}isAuthenticated(){return!!this.storage.getTokens()&&!!this.storage.getUser()}getState(){return{isAuthenticated:this.isAuthenticated(),user:this.getUser(),tokens:this.storage.getTokens()}}setupTokenRefresh(){setInterval(async()=>{this.shouldRefreshToken()&&await this.refreshToken()},6e4)}shouldRefreshToken(){const e=this.storage.getTokens();return!(!e||!this.tokenIssuedAt)&&(t=e.expiresIn,s=this.tokenIssuedAt,Date.now()>=s+1e3*t-3e5);var t,s}async refreshToken(){const e=this.storage.getTokens();if(!e?.refreshToken)throw new Error("No refresh token available");try{const t=await this.api.refreshAccessToken(e.refreshToken,this.config.clientId,this.config.clientSecret);this.tokenIssuedAt=Date.now(),this.storage.setTokens(t),this.emit("token-refresh",t)}catch(e){throw this.emit("error",e),await this.logout(),e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){const s={type:e,data:t};this.listeners.get(e)?.forEach(e=>e(s))}};
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:!0});const e="custos_";class t{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(t){this.storage.setItem(`${e}tokens`,JSON.stringify(t)),this.storage.setItem(`${e}token_issued_at`,Date.now().toString())}getTokens(){const t=this.storage.getItem(`${e}tokens`);return t?JSON.parse(t):null}getTokenIssuedAt(){const t=this.storage.getItem(`${e}token_issued_at`);return t?parseInt(t,10):null}setUser(t){this.storage.setItem(`${e}user`,JSON.stringify(t))}getUser(){const t=this.storage.getItem(`${e}user`);return t?JSON.parse(t):null}setState(t,r){this.storage.setItem(`${e}${t}`,r)}getState(t){return this.storage.getItem(`${e}${t}`)}removeState(t){this.storage.removeItem(`${e}${t}`)}setCodeVerifier(e){this.setState("code_verifier",e)}getCodeVerifier(){return this.getState("code_verifier")}removeCodeVerifier(){this.removeState("code_verifier")}setCodeChallenge(e){this.setState("code_challenge",e)}getCodeChallenge(){return this.getState("code_challenge")}removeCodeChallenge(){this.removeState("code_challenge")}clear(){this.storage.removeItem(`${e}tokens`),this.storage.removeItem(`${e}token_issued_at`),this.storage.removeItem(`${e}user`),this.removeState("oauth_state"),this.removeCodeVerifier(),this.removeCodeChallenge()}hasValidToken(){const e=this.getTokens(),t=this.getTokenIssuedAt();if(!e||!t)return!1;return Date.now()<t+1e3*e.expiresIn}}class r{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,r,s,o){const i={grant_type:"authorization_code",code:e,client_id:t,redirect_uri:r};s&&(i.code_verifier=s),o&&(i.client_secret=o);const n=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(i).toString()});if(!n.ok){const e=await n.json().catch(()=>({error:"unknown_error",error_description:"Failed to exchange code for tokens"}));throw new Error(e.error_description||e.error||"Token exchange failed")}const a=await n.json(),c=a.data||a;return{accessToken:c.access_token,refreshToken:c.refresh_token,expiresIn:c.expires_in,tokenType:c.token_type||"Bearer"}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/api/v1/system/users/profile`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");const r=await t.json();return r.data||r}async refreshAccessToken(e,t,r){const s={grant_type:"refresh_token",refresh_token:e,client_id:t};r&&(s.client_secret=r);const o=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(s).toString()});if(!o.ok)throw new Error("Failed to refresh token");const i=await o.json(),n=i.data||i;return{accessToken:n.access_token,refreshToken:n.refresh_token||e,expiresIn:n.expires_in,tokenType:n.token_type||"Bearer"}}async logout(e){await fetch(`${this.baseUrl}/api/v1/auth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}async validateToken(e){try{return(await fetch(`${this.baseUrl}/api/v1/auth/validate`,{headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}}function s(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}async function o(e){const t=(new TextEncoder).encode(e);return function(e){const t=new Uint8Array(e),r=Array.from(t,e=>String.fromCharCode(e)).join("");return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}(await crypto.subtle.digest("SHA-256",t))}exports.Custos=class{constructor(e){this.tokenExpiryTimer=null;const o=function(e){return e?Array.isArray(e)?e:"string"==typeof e?e.split(" "):["openid","profile","email"]:["openid","profile","email"]}(e.scope);this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:o,responseType:e.responseType||"code",state:e.state||s(),usePKCE:!1!==e.usePKCE,codeChallengeMethod:e.codeChallengeMethod||"S256",grantType:e.grantType||"authorization_code"},this.storage=new t,this.api=new r(this.config.apiUrl),this.listeners=new Map,"undefined"!=typeof window&&(this.handleCallback(),this.setupTokenExpiryMonitoring())}async login(e){const t=this.config.state;this.storage.setState("oauth_state",t);const r={response_type:this.config.responseType,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:Array.isArray(this.config.scope)?this.config.scope.join(" "):this.config.scope,state:t,...e};if(this.config.usePKCE){const e=function(){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=Math.floor(86*Math.random())+43;let r="";for(let s=0;s<t;s++)r+=e.charAt(Math.floor(66*Math.random()));return r}(),t=await o(e);this.storage.setCodeVerifier(e),this.storage.setCodeChallenge(t),r.code_challenge=t,r.code_challenge_method=this.config.codeChallengeMethod}const s=`${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(r)}`;window.location.href=s}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.clearTokenExpiryTimer(),this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,r)=>{t[r]=e}),t}(window.location.href),t=e.error;if(t){const r=e.error_description||t;throw this.emit("error",{error:t,error_description:r}),new Error(r)}const r=e.code;if(!r)return;if(e.state!==this.storage.getState("oauth_state"))throw this.emit("error",{error:"invalid_state",error_description:"State parameter mismatch"}),new Error("Invalid state parameter");this.storage.removeState("oauth_state");try{const e=this.config.usePKCE&&this.storage.getCodeVerifier()||void 0,t=await this.api.exchangeCodeForTokens(r,this.config.clientId,this.config.redirectUri,e,this.config.clientSecret);this.storage.setTokens(t);const s=await this.api.getUserInfo(t.accessToken);this.storage.setUser(s),this.config.usePKCE&&(this.storage.removeCodeVerifier(),this.storage.removeCodeChallenge()),this.setupTokenExpiryMonitoring(),this.emit("login",{user:s,tokens:t}),window.history.replaceState({},document.title,window.location.pathname+window.location.hash)}catch(t){throw this.emit("error",t),t}}getUser(){return this.storage.getUser()}getAccessToken(){return this.storage.getTokens()?.accessToken||null}getRefreshToken(){return this.storage.getTokens()?.refreshToken||null}isAuthenticated(){return this.storage.hasValidToken()&&!!this.storage.getUser()}getState(){return{isAuthenticated:this.isAuthenticated(),user:this.getUser(),tokens:this.storage.getTokens()}}async validateToken(){const e=this.getAccessToken();if(!e)return!1;try{return await this.api.validateToken(e)}catch{return!1}}setupTokenExpiryMonitoring(){this.clearTokenExpiryTimer();const e=this.storage.getTokens(),t=this.storage.getTokenIssuedAt();if(!e||!t)return;const r=1e3*(e.expiresIn-300);r>0&&(this.tokenExpiryTimer=setTimeout(async()=>{try{await this.refreshToken()}catch(e){this.emit("token-expired",e),await this.logout()}},r))}clearTokenExpiryTimer(){this.tokenExpiryTimer&&(clearTimeout(this.tokenExpiryTimer),this.tokenExpiryTimer=null)}async refreshToken(){const e=this.storage.getTokens();if(!e?.refreshToken)throw new Error("No refresh token available");try{const t=await this.api.refreshAccessToken(e.refreshToken,this.config.clientId,this.config.clientSecret);this.storage.setTokens(t),this.setupTokenExpiryMonitoring(),this.emit("token-refresh",t)}catch(e){throw this.emit("error",e),e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){const r={type:e,data:t};this.listeners.get(e)?.forEach(e=>e(r))}clearStorage(){this.storage.clear()}destroy(){this.clearTokenExpiryTimer(),this.listeners.clear()}};
2
2
  //# sourceMappingURL=index.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs.js","sources":["../src/storage.ts","../src/api.ts","../src/Custos.ts","../src/utils.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\r\n\r\nconst STORAGE_PREFIX = 'custos_';\r\n\r\nexport class Storage {\r\n\tprivate storage: globalThis.Storage;\r\n\r\n\tconstructor(useSessionStorage = false) {\r\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\r\n\t}\r\n\r\n\tsetTokens(tokens: AuthTokens): void {\r\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\r\n\t}\r\n\r\n\tgetTokens(): AuthTokens | null {\r\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\r\n\t\treturn data ? JSON.parse(data) : null;\r\n\t}\r\n\r\n\tsetUser(user: User): void {\r\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\r\n\t}\r\n\r\n\tgetUser(): User | null {\r\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\r\n\t\treturn data ? JSON.parse(data) : null;\r\n\t}\r\n\r\n\tclear(): void {\r\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\r\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\r\n\t}\r\n\r\n\tsetState(key: string, value: string): void {\r\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\r\n\t}\r\n\r\n\tgetState(key: string): string | null {\r\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\r\n\t}\r\n\r\n\tremoveState(key: string): void {\r\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\r\n\t}\r\n}","import { AuthTokens, User } from './types';\r\n\r\nexport class ApiClient {\r\n\tprivate baseUrl: string;\r\n\r\n\tconstructor(baseUrl: string) {\r\n\t\tthis.baseUrl = baseUrl;\r\n\t}\r\n\r\n\tasync exchangeCodeForTokens(\r\n\t\tcode: string,\r\n\t\tclientId: string,\r\n\t\tredirectUri: string,\r\n\t\tclientSecret?: string\r\n\t): Promise<AuthTokens> {\r\n\t\tconst response = await fetch(`${this.baseUrl}/oauth/token`, {\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\t'Content-Type': 'application/json',\r\n\t\t\t},\r\n\t\t\tbody: JSON.stringify({\r\n\t\t\t\tgrant_type: 'authorization_code',\r\n\t\t\t\tcode,\r\n\t\t\t\tclient_id: clientId,\r\n\t\t\t\tclient_secret: clientSecret,\r\n\t\t\t\tredirect_uri: redirectUri,\r\n\t\t\t}),\r\n\t\t});\r\n\r\n\t\tif (!response.ok) {\r\n\t\t\tthrow new Error('Failed to exchange code for tokens');\r\n\t\t}\r\n\r\n\t\tconst data = await response.json();\r\n\t\treturn {\r\n\t\t\taccessToken: data.access_token,\r\n\t\t\trefreshToken: data.refresh_token,\r\n\t\t\texpiresIn: data.expires_in,\r\n\t\t\ttokenType: data.token_type,\r\n\t\t};\r\n\t}\r\n\r\n\tasync getUserInfo(accessToken: string): Promise<User> {\r\n\t\tconst response = await fetch(`${this.baseUrl}/oauth/userinfo`, {\r\n\t\t\theaders: {\r\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\r\n\t\t\t},\r\n\t\t});\r\n\r\n\t\tif (!response.ok) {\r\n\t\t\tthrow new Error('Failed to get user info');\r\n\t\t}\r\n\r\n\t\treturn response.json();\r\n\t}\r\n\r\n\tasync refreshAccessToken(\r\n\t\trefreshToken: string,\r\n\t\tclientId: string,\r\n\t\tclientSecret?: string\r\n\t): Promise<AuthTokens> {\r\n\t\tconst response = await fetch(`${this.baseUrl}/oauth/token`, {\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\t'Content-Type': 'application/json',\r\n\t\t\t},\r\n\t\t\tbody: JSON.stringify({\r\n\t\t\t\tgrant_type: 'refresh_token',\r\n\t\t\t\trefresh_token: refreshToken,\r\n\t\t\t\tclient_id: clientId,\r\n\t\t\t\tclient_secret: clientSecret,\r\n\t\t\t}),\r\n\t\t});\r\n\r\n\t\tif (!response.ok) {\r\n\t\t\tthrow new Error('Failed to refresh token');\r\n\t\t}\r\n\r\n\t\tconst data = await response.json();\r\n\t\treturn {\r\n\t\t\taccessToken: data.access_token,\r\n\t\t\trefreshToken: data.refresh_token,\r\n\t\t\texpiresIn: data.expires_in,\r\n\t\t\ttokenType: data.token_type,\r\n\t\t};\r\n\t}\r\n\r\n\tasync logout(accessToken: string): Promise<void> {\r\n\t\tawait fetch(`${this.baseUrl}/oauth/revoke`, {\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\r\n\t\t\t},\r\n\t\t});\r\n\t}\r\n}","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\r\nimport { Storage } from './storage';\r\nimport { ApiClient } from './api';\r\nimport { generateState, parseQueryString, isTokenExpired } from './utils';\r\n\r\nexport class Custos {\r\n\tprivate config: Required<CustosConfig>;\r\n\tprivate storage: Storage;\r\n\tprivate api: ApiClient;\r\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\r\n\tprivate tokenIssuedAt: number | null = null;\r\n\r\n\tconstructor(config: CustosConfig) {\r\n\t\tthis.config = {\r\n\t\t\tclientId: config.clientId,\r\n\t\t\tclientSecret: config.clientSecret || '',\r\n\t\t\tredirectUri: config.redirectUri,\r\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\r\n\t\t\tscope: config.scope || ['openid', 'profile', 'email'],\r\n\t\t};\r\n\r\n\t\tthis.storage = new Storage();\r\n\t\tthis.api = new ApiClient(this.config.apiUrl);\r\n\t\tthis.listeners = new Map();\r\n\r\n\t\t// Handle callback automatically\r\n\t\tthis.handleCallback();\r\n\r\n\t\t// Setup token refresh\r\n\t\tthis.setupTokenRefresh();\r\n\t}\r\n\r\n\t// Authentication Methods\r\n\tasync login(): Promise<void> {\r\n\t\tconst state = generateState();\r\n\t\tthis.storage.setState('oauth_state', state);\r\n\r\n\t\tconst params = new URLSearchParams({\r\n\t\t\tresponse_type: 'code',\r\n\t\t\tclient_id: this.config.clientId,\r\n\t\t\tredirect_uri: this.config.redirectUri,\r\n\t\t\tscope: this.config.scope.join(' '),\r\n\t\t\tstate,\r\n\t\t});\r\n\r\n\t\twindow.location.href = `${this.config.apiUrl}/oauth/authorize?${params}`;\r\n\t}\r\n\r\n\tasync logout(): Promise<void> {\r\n\t\tconst tokens = this.storage.getTokens();\r\n\r\n\t\tif (tokens?.accessToken) {\r\n\t\t\ttry {\r\n\t\t\t\tawait this.api.logout(tokens.accessToken);\r\n\t\t\t} catch (error) {\r\n\t\t\t\tconsole.error('Logout error:', error);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.storage.clear();\r\n\t\tthis.emit('logout', null);\r\n\t}\r\n\r\n\tasync handleCallback(): Promise<void> {\r\n\t\tconst params = parseQueryString(window.location.href);\r\n\t\tconst code = params.code;\r\n\t\tconst state = params.state;\r\n\r\n\t\tif (!code) return;\r\n\r\n\t\tconst savedState = this.storage.getState('oauth_state');\r\n\t\tif (state !== savedState) {\r\n\t\t\tthrow new Error('Invalid state parameter');\r\n\t\t}\r\n\r\n\t\tthis.storage.removeState('oauth_state');\r\n\r\n\t\ttry {\r\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\r\n\t\t\t\tcode,\r\n\t\t\t\tthis.config.clientId,\r\n\t\t\t\tthis.config.redirectUri,\r\n\t\t\t\tthis.config.clientSecret\r\n\t\t\t);\r\n\r\n\t\t\tthis.tokenIssuedAt = Date.now();\r\n\t\t\tthis.storage.setTokens(tokens);\r\n\r\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\r\n\t\t\tthis.storage.setUser(user);\r\n\r\n\t\t\tthis.emit('login', { user, tokens });\r\n\r\n\t\t\t// Clean URL\r\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname);\r\n\t\t} catch (error) {\r\n\t\t\tthis.emit('error', error);\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t// User Methods\r\n\tgetUser(): User | null {\r\n\t\treturn this.storage.getUser();\r\n\t}\r\n\r\n\tgetAccessToken(): string | null {\r\n\t\treturn this.storage.getTokens()?.accessToken || null;\r\n\t}\r\n\r\n\tisAuthenticated(): boolean {\r\n\t\treturn !!this.storage.getTokens() && !!this.storage.getUser();\r\n\t}\r\n\r\n\tgetState(): AuthState {\r\n\t\treturn {\r\n\t\t\tisAuthenticated: this.isAuthenticated(),\r\n\t\t\tuser: this.getUser(),\r\n\t\t\ttokens: this.storage.getTokens(),\r\n\t\t};\r\n\t}\r\n\r\n\t// Token Refresh\r\n\tprivate setupTokenRefresh(): void {\r\n\t\tsetInterval(async () => {\r\n\t\t\tif (this.shouldRefreshToken()) {\r\n\t\t\t\tawait this.refreshToken();\r\n\t\t\t}\r\n\t\t}, 60000); // Check every minute\r\n\t}\r\n\r\n\tprivate shouldRefreshToken(): boolean {\r\n\t\tconst tokens = this.storage.getTokens();\r\n\t\tif (!tokens || !this.tokenIssuedAt) return false;\r\n\r\n\t\treturn isTokenExpired(tokens.expiresIn, this.tokenIssuedAt);\r\n\t}\r\n\r\n\tasync refreshToken(): Promise<void> {\r\n\t\tconst tokens = this.storage.getTokens();\r\n\t\tif (!tokens?.refreshToken) {\r\n\t\t\tthrow new Error('No refresh token available');\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\r\n\t\t\t\ttokens.refreshToken,\r\n\t\t\t\tthis.config.clientId,\r\n\t\t\t\tthis.config.clientSecret\r\n\t\t\t);\r\n\r\n\t\t\tthis.tokenIssuedAt = Date.now();\r\n\t\t\tthis.storage.setTokens(newTokens);\r\n\r\n\t\t\tthis.emit('token-refresh', newTokens);\r\n\t\t} catch (error) {\r\n\t\t\tthis.emit('error', error);\r\n\t\t\t// If refresh fails, logout\r\n\t\t\tawait this.logout();\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t// Event Handling\r\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\r\n\t\tif (!this.listeners.has(event)) {\r\n\t\t\tthis.listeners.set(event, new Set());\r\n\t\t}\r\n\t\tthis.listeners.get(event)!.add(callback);\r\n\t}\r\n\r\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\r\n\t\tthis.listeners.get(event)?.delete(callback);\r\n\t}\r\n\r\n\tprivate emit(type: AuthEventType, data?: any): void {\r\n\t\tconst event: AuthEvent = { type, data };\r\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\r\n\t}\r\n}","export function generateState(): string {\r\n\tconst array = new Uint8Array(32);\r\n\tcrypto.getRandomValues(array);\r\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\r\n}\r\n\r\nexport function parseQueryString(url: string): Record<string, string> {\r\n\tconst params: Record<string, string> = {};\r\n\tconst searchParams = new URL(url).searchParams;\r\n\tsearchParams.forEach((value, key) => {\r\n\t\tparams[key] = value;\r\n\t});\r\n\treturn params;\r\n}\r\n\r\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\r\n\tconst now = Date.now();\r\n\tconst expirationTime = issuedAt + expiresIn * 1000;\r\n\t// Refresh 5 minutes before expiration\r\n\treturn now >= expirationTime - 5 * 60 * 1000;\r\n}"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","getTokens","data","getItem","parse","setUser","user","getUser","clear","removeItem","setState","key","value","getState","removeState","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","response","fetch","method","headers","body","grant_type","client_id","client_secret","redirect_uri","ok","Error","json","accessToken","access_token","refreshToken","refresh_token","expiresIn","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","config","tokenIssuedAt","apiUrl","scope","api","listeners","Map","handleCallback","setupTokenRefresh","login","state","array","Uint8Array","crypto","getRandomValues","Array","from","byte","toString","padStart","join","generateState","params","URLSearchParams","response_type","window","location","href","error","console","emit","url","URL","searchParams","forEach","parseQueryString","Date","now","history","replaceState","document","title","pathname","getAccessToken","isAuthenticated","setInterval","async","shouldRefreshToken","issuedAt","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs.js","sources":["../src/storage.ts","../src/api.ts","../src/utils.ts","../src/Custos.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\n\nconst STORAGE_PREFIX = 'custos_';\n\nexport class Storage {\n\tprivate storage: globalThis.Storage;\n\n\tconstructor(useSessionStorage = false) {\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\n\t}\n\n\t// Tokens\n\tsetTokens(tokens: AuthTokens): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());\n\t}\n\n\tgetTokens(): AuthTokens | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\tgetTokenIssuedAt(): number | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\treturn data ? parseInt(data, 10) : null;\n\t}\n\n\t// User\n\tsetUser(user: User): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\n\t}\n\n\tgetUser(): User | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\t// State & PKCE\n\tsetState(key: string, value: string): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\n\t}\n\n\tgetState(key: string): string | null {\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\tremoveState(key: string): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\t// PKCE specific\n\tsetCodeVerifier(codeVerifier: string): void {\n\t\tthis.setState('code_verifier', codeVerifier);\n\t}\n\n\tgetCodeVerifier(): string | null {\n\t\treturn this.getState('code_verifier');\n\t}\n\n\tremoveCodeVerifier(): void {\n\t\tthis.removeState('code_verifier');\n\t}\n\n\tsetCodeChallenge(codeChallenge: string): void {\n\t\tthis.setState('code_challenge', codeChallenge);\n\t}\n\n\tgetCodeChallenge(): string | null {\n\t\treturn this.getState('code_challenge');\n\t}\n\n\tremoveCodeChallenge(): void {\n\t\tthis.removeState('code_challenge');\n\t}\n\n\t// Clear all\n\tclear(): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\n\t\tthis.removeState('oauth_state');\n\t\tthis.removeCodeVerifier();\n\t\tthis.removeCodeChallenge();\n\t}\n\n\t// Validation\n\thasValidToken(): boolean {\n\t\tconst tokens = this.getTokens();\n\t\tconst issuedAt = this.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return false;\n\n\t\tconst now = Date.now();\n\t\tconst expirationTime = issuedAt + tokens.expiresIn * 1000;\n\n\t\treturn now < expirationTime;\n\t}\n}\n","import { AuthTokens, User } from './types';\n\nexport class ApiClient {\n\tprivate baseUrl: string;\n\n\tconstructor(baseUrl: string) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tasync exchangeCodeForTokens(\n\t\tcode: string,\n\t\tclientId: string,\n\t\tredirectUri: string,\n\t\tcodeVerifier?: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t};\n\n\t\t// Add PKCE code_verifier if present\n\t\tif (codeVerifier) {\n\t\t\tbody.code_verifier = codeVerifier;\n\t\t}\n\n\t\t// Add client_secret if present (for confidential clients)\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({\n\t\t\t\terror: 'unknown_error',\n\t\t\t\terror_description: 'Failed to exchange code for tokens'\n\t\t\t}));\n\n\t\t\tthrow new Error(errorData.error_description || errorData.error || 'Token exchange failed');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result; // Support both {data: {...}} and direct response\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token,\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync getUserInfo(accessToken: string): Promise<User> {\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/system/users/profile`, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to get user info');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\treturn result.data || result;\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t\tclientId: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: clientId,\n\t\t};\n\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to refresh token');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result;\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync logout(accessToken: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/api/v1/auth/revoke`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync validateToken(accessToken: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn response.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","export function generateState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseQueryString(url: string): Record<string, string> {\n\tconst params: Record<string, string> = {};\n\tconst searchParams = new URL(url).searchParams;\n\tsearchParams.forEach((value, key) => {\n\t\tparams[key] = value;\n\t});\n\treturn params;\n}\n\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\n\tconst now = Date.now();\n\tconst expirationTime = issuedAt + expiresIn * 1000;\n\t// Refresh 5 minutes before expiration\n\treturn now >= expirationTime - 5 * 60 * 1000;\n}\n\n// PKCE utilities\nexport function generateCodeVerifier(): string {\n\tconst charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\tconst length = Math.floor(Math.random() * 86) + 43; // 43-128 characters\n\tlet verifier = '';\n\tfor (let i = 0; i < length; i++) {\n\t\tverifier += charset.charAt(Math.floor(Math.random() * charset.length));\n\t}\n\treturn verifier;\n}\n\nexport async function generateCodeChallenge(codeVerifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(codeVerifier);\n\tconst digest = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(digest);\n}\n\nfunction base64UrlEncode(digest: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(digest);\n\tconst binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nexport function normalizeScope(scope?: string | string[]): string[] {\n\tif (!scope) return ['openid', 'profile', 'email'];\n\tif (Array.isArray(scope)) return scope;\n\tif (typeof scope === 'string') return scope.split(' ');\n\treturn ['openid', 'profile', 'email'];\n}\n","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\nimport { Storage } from './storage';\nimport { ApiClient } from './api';\nimport {\n\tgenerateState,\n\tparseQueryString,\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tnormalizeScope\n} from './utils';\n\nexport class Custos {\n\tprivate config: Required<CustosConfig>;\n\tprivate storage: Storage;\n\tprivate api: ApiClient;\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\n\tprivate tokenExpiryTimer: any = null;\n\n\tconstructor(config: CustosConfig) {\n\t\t// Normalize scope\n\t\tconst scope = normalizeScope(config.scope);\n\n\t\tthis.config = {\n\t\t\tclientId: config.clientId,\n\t\t\tclientSecret: config.clientSecret || '',\n\t\t\tredirectUri: config.redirectUri,\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\n\t\t\tscope,\n\t\t\tresponseType: config.responseType || 'code',\n\t\t\tstate: config.state || generateState(),\n\t\t\tusePKCE: config.usePKCE !== false, // Default to true\n\t\t\tcodeChallengeMethod: config.codeChallengeMethod || 'S256',\n\t\t\tgrantType: config.grantType || 'authorization_code',\n\t\t};\n\n\t\tthis.storage = new Storage();\n\t\tthis.api = new ApiClient(this.config.apiUrl);\n\t\tthis.listeners = new Map();\n\n\t\t// Handle callback automatically\n\t\tif (typeof window !== 'undefined') {\n\t\t\tthis.handleCallback();\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t}\n\t}\n\n\t// ==================== Authentication Methods ====================\n\n\tasync login(additionalParams?: Record<string, string>): Promise<void> {\n\t\tconst state = this.config.state;\n\t\tthis.storage.setState('oauth_state', state);\n\n\t\tconst params: Record<string, string> = {\n\t\t\tresponse_type: this.config.responseType,\n\t\t\tclient_id: this.config.clientId,\n\t\t\tredirect_uri: this.config.redirectUri,\n\t\t\tscope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,\n\t\t\tstate,\n\t\t\t...additionalParams,\n\t\t};\n\n\t\t// Add PKCE if enabled\n\t\tif (this.config.usePKCE) {\n\t\t\tconst codeVerifier = generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t\t\tthis.storage.setCodeVerifier(codeVerifier);\n\t\t\tthis.storage.setCodeChallenge(codeChallenge);\n\n\t\t\tparams.code_challenge = codeChallenge;\n\t\t\tparams.code_challenge_method = this.config.codeChallengeMethod;\n\t\t}\n\n\t\tconst authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;\n\t\twindow.location.href = authUrl;\n\t}\n\n\tasync logout(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\n\t\tif (tokens?.accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.api.logout(tokens.accessToken);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Logout error:', error);\n\t\t\t}\n\t\t}\n\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.storage.clear();\n\t\tthis.emit('logout', null);\n\t}\n\n\tasync handleCallback(): Promise<void> {\n\t\tconst params = parseQueryString(window.location.href);\n\n\t\t// Check for errors\n\t\tconst error = params.error;\n\t\tif (error) {\n\t\t\tconst errorDescription = params.error_description || error;\n\t\t\tthis.emit('error', { error, error_description: errorDescription });\n\t\t\tthrow new Error(errorDescription);\n\t\t}\n\n\t\t// Check for authorization code\n\t\tconst code = params.code;\n\t\tif (!code) return;\n\n\t\t// Validate state\n\t\tconst state = params.state;\n\t\tconst savedState = this.storage.getState('oauth_state');\n\t\tif (state !== savedState) {\n\t\t\tthis.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });\n\t\t\tthrow new Error('Invalid state parameter');\n\t\t}\n\n\t\tthis.storage.removeState('oauth_state');\n\n\t\ttry {\n\t\t\t// Get code_verifier if using PKCE\n\t\t\tconst codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;\n\n\t\t\t// Exchange code for tokens\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\n\t\t\t\tcode,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.redirectUri,\n\t\t\t\tcodeVerifier,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(tokens);\n\n\t\t\t// Get user info\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\n\t\t\tthis.storage.setUser(user);\n\n\t\t\t// Clean up PKCE data\n\t\t\tif (this.config.usePKCE) {\n\t\t\t\tthis.storage.removeCodeVerifier();\n\t\t\t\tthis.storage.removeCodeChallenge();\n\t\t\t}\n\n\t\t\t// Setup token expiry monitoring\n\t\t\tthis.setupTokenExpiryMonitoring();\n\n\t\t\tthis.emit('login', { user, tokens });\n\n\t\t\t// Clean URL (remove query params)\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname + window.location.hash);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== User Methods ====================\n\n\tgetUser(): User | null {\n\t\treturn this.storage.getUser();\n\t}\n\n\tgetAccessToken(): string | null {\n\t\treturn this.storage.getTokens()?.accessToken || null;\n\t}\n\n\tgetRefreshToken(): string | null {\n\t\treturn this.storage.getTokens()?.refreshToken || null;\n\t}\n\n\tisAuthenticated(): boolean {\n\t\treturn this.storage.hasValidToken() && !!this.storage.getUser();\n\t}\n\n\tgetState(): AuthState {\n\t\treturn {\n\t\t\tisAuthenticated: this.isAuthenticated(),\n\t\t\tuser: this.getUser(),\n\t\t\ttokens: this.storage.getTokens(),\n\t\t};\n\t}\n\n\tasync validateToken(): Promise<boolean> {\n\t\tconst accessToken = this.getAccessToken();\n\t\tif (!accessToken) return false;\n\n\t\ttry {\n\t\t\treturn await this.api.validateToken(accessToken);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==================== Token Refresh ====================\n\n\tprivate setupTokenExpiryMonitoring(): void {\n\t\tthis.clearTokenExpiryTimer();\n\n\t\tconst tokens = this.storage.getTokens();\n\t\tconst issuedAt = this.storage.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return;\n\n\t\t// Refresh 5 minutes before expiry\n\t\tconst timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer\n\n\t\tif (timeUntilRefresh > 0) {\n\t\t\tthis.tokenExpiryTimer = setTimeout(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.refreshToken();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.emit('token-expired', error);\n\t\t\t\t\tawait this.logout();\n\t\t\t\t}\n\t\t\t}, timeUntilRefresh);\n\t\t}\n\t}\n\n\tprivate clearTokenExpiryTimer(): void {\n\t\tif (this.tokenExpiryTimer) {\n\t\t\tclearTimeout(this.tokenExpiryTimer);\n\t\t\tthis.tokenExpiryTimer = null;\n\t\t}\n\t}\n\n\tasync refreshToken(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\t\tif (!tokens?.refreshToken) {\n\t\t\tthrow new Error('No refresh token available');\n\t\t}\n\n\t\ttry {\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\n\t\t\t\ttokens.refreshToken,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(newTokens);\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t\tthis.emit('token-refresh', newTokens);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== Event Handling ====================\n\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)!.add(callback);\n\t}\n\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tthis.listeners.get(event)?.delete(callback);\n\t}\n\n\tprivate emit(type: AuthEventType, data?: any): void {\n\t\tconst event: AuthEvent = { type, data };\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\n\t}\n\n\t// ==================== Utility Methods ====================\n\n\tclearStorage(): void {\n\t\tthis.storage.clear();\n\t}\n\n\tdestroy(): void {\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.listeners.clear();\n\t}\n}\n"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","Date","now","toString","getTokens","data","getItem","parse","getTokenIssuedAt","parseInt","setUser","user","getUser","setState","key","value","getState","removeState","removeItem","setCodeVerifier","codeVerifier","getCodeVerifier","removeCodeVerifier","setCodeChallenge","codeChallenge","getCodeChallenge","removeCodeChallenge","clear","hasValidToken","issuedAt","expiresIn","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","body","grant_type","client_id","redirect_uri","code_verifier","client_secret","response","fetch","method","headers","URLSearchParams","ok","errorData","json","catch","error","error_description","Error","result","accessToken","access_token","refreshToken","refresh_token","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","validateToken","generateState","array","Uint8Array","crypto","getRandomValues","Array","from","byte","padStart","join","async","generateCodeChallenge","TextEncoder","encode","digest","bytes","binary","String","fromCharCode","btoa","replace","base64UrlEncode","subtle","config","tokenExpiryTimer","scope","isArray","split","normalizeScope","apiUrl","responseType","state","usePKCE","codeChallengeMethod","grantType","api","listeners","Map","window","handleCallback","setupTokenExpiryMonitoring","login","additionalParams","params","response_type","charset","length","Math","floor","random","verifier","i","charAt","generateCodeVerifier","code_challenge","code_challenge_method","authUrl","location","href","console","clearTokenExpiryTimer","emit","url","URL","searchParams","forEach","parseQueryString","errorDescription","undefined","history","replaceState","document","title","pathname","hash","getAccessToken","getRefreshToken","isAuthenticated","timeUntilRefresh","setTimeout","clearTimeout","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type","clearStorage","destroy"],"mappings":"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"}
package/dist/index.esm.js CHANGED
@@ -1,2 +1,2 @@
1
- const e="custos_";class t{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(t){this.storage.setItem(`${e}tokens`,JSON.stringify(t))}getTokens(){const t=this.storage.getItem(`${e}tokens`);return t?JSON.parse(t):null}setUser(t){this.storage.setItem(`${e}user`,JSON.stringify(t))}getUser(){const t=this.storage.getItem(`${e}user`);return t?JSON.parse(t):null}clear(){this.storage.removeItem(`${e}tokens`),this.storage.removeItem(`${e}user`)}setState(t,s){this.storage.setItem(`${e}${t}`,s)}getState(t){return this.storage.getItem(`${e}${t}`)}removeState(t){this.storage.removeItem(`${e}${t}`)}}class s{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,s,o){const r=await fetch(`${this.baseUrl}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"authorization_code",code:e,client_id:t,client_secret:o,redirect_uri:s})});if(!r.ok)throw new Error("Failed to exchange code for tokens");const n=await r.json();return{accessToken:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,tokenType:n.token_type}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/oauth/userinfo`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");return t.json()}async refreshAccessToken(e,t,s){const o=await fetch(`${this.baseUrl}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"refresh_token",refresh_token:e,client_id:t,client_secret:s})});if(!o.ok)throw new Error("Failed to refresh token");const r=await o.json();return{accessToken:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,tokenType:r.token_type}}async logout(e){await fetch(`${this.baseUrl}/oauth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}}class o{constructor(e){this.tokenIssuedAt=null,this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:e.scope||["openid","profile","email"]},this.storage=new t,this.api=new s(this.config.apiUrl),this.listeners=new Map,this.handleCallback(),this.setupTokenRefresh()}async login(){const e=function(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}();this.storage.setState("oauth_state",e);const t=new URLSearchParams({response_type:"code",client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:this.config.scope.join(" "),state:e});window.location.href=`${this.config.apiUrl}/oauth/authorize?${t}`}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,s)=>{t[s]=e}),t}(window.location.href),t=e.code,s=e.state;if(!t)return;if(s!==this.storage.getState("oauth_state"))throw new Error("Invalid state parameter");this.storage.removeState("oauth_state");try{const e=await this.api.exchangeCodeForTokens(t,this.config.clientId,this.config.redirectUri,this.config.clientSecret);this.tokenIssuedAt=Date.now(),this.storage.setTokens(e);const s=await this.api.getUserInfo(e.accessToken);this.storage.setUser(s),this.emit("login",{user:s,tokens:e}),window.history.replaceState({},document.title,window.location.pathname)}catch(e){throw this.emit("error",e),e}}getUser(){return this.storage.getUser()}getAccessToken(){return this.storage.getTokens()?.accessToken||null}isAuthenticated(){return!!this.storage.getTokens()&&!!this.storage.getUser()}getState(){return{isAuthenticated:this.isAuthenticated(),user:this.getUser(),tokens:this.storage.getTokens()}}setupTokenRefresh(){setInterval(async()=>{this.shouldRefreshToken()&&await this.refreshToken()},6e4)}shouldRefreshToken(){const e=this.storage.getTokens();return!(!e||!this.tokenIssuedAt)&&(t=e.expiresIn,s=this.tokenIssuedAt,Date.now()>=s+1e3*t-3e5);var t,s}async refreshToken(){const e=this.storage.getTokens();if(!e?.refreshToken)throw new Error("No refresh token available");try{const t=await this.api.refreshAccessToken(e.refreshToken,this.config.clientId,this.config.clientSecret);this.tokenIssuedAt=Date.now(),this.storage.setTokens(t),this.emit("token-refresh",t)}catch(e){throw this.emit("error",e),await this.logout(),e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){const s={type:e,data:t};this.listeners.get(e)?.forEach(e=>e(s))}}export{o as Custos};
1
+ const e="custos_";class t{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(t){this.storage.setItem(`${e}tokens`,JSON.stringify(t)),this.storage.setItem(`${e}token_issued_at`,Date.now().toString())}getTokens(){const t=this.storage.getItem(`${e}tokens`);return t?JSON.parse(t):null}getTokenIssuedAt(){const t=this.storage.getItem(`${e}token_issued_at`);return t?parseInt(t,10):null}setUser(t){this.storage.setItem(`${e}user`,JSON.stringify(t))}getUser(){const t=this.storage.getItem(`${e}user`);return t?JSON.parse(t):null}setState(t,r){this.storage.setItem(`${e}${t}`,r)}getState(t){return this.storage.getItem(`${e}${t}`)}removeState(t){this.storage.removeItem(`${e}${t}`)}setCodeVerifier(e){this.setState("code_verifier",e)}getCodeVerifier(){return this.getState("code_verifier")}removeCodeVerifier(){this.removeState("code_verifier")}setCodeChallenge(e){this.setState("code_challenge",e)}getCodeChallenge(){return this.getState("code_challenge")}removeCodeChallenge(){this.removeState("code_challenge")}clear(){this.storage.removeItem(`${e}tokens`),this.storage.removeItem(`${e}token_issued_at`),this.storage.removeItem(`${e}user`),this.removeState("oauth_state"),this.removeCodeVerifier(),this.removeCodeChallenge()}hasValidToken(){const e=this.getTokens(),t=this.getTokenIssuedAt();if(!e||!t)return!1;return Date.now()<t+1e3*e.expiresIn}}class r{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,r,s,o){const i={grant_type:"authorization_code",code:e,client_id:t,redirect_uri:r};s&&(i.code_verifier=s),o&&(i.client_secret=o);const n=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(i).toString()});if(!n.ok){const e=await n.json().catch(()=>({error:"unknown_error",error_description:"Failed to exchange code for tokens"}));throw new Error(e.error_description||e.error||"Token exchange failed")}const a=await n.json(),c=a.data||a;return{accessToken:c.access_token,refreshToken:c.refresh_token,expiresIn:c.expires_in,tokenType:c.token_type||"Bearer"}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/api/v1/system/users/profile`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");const r=await t.json();return r.data||r}async refreshAccessToken(e,t,r){const s={grant_type:"refresh_token",refresh_token:e,client_id:t};r&&(s.client_secret=r);const o=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(s).toString()});if(!o.ok)throw new Error("Failed to refresh token");const i=await o.json(),n=i.data||i;return{accessToken:n.access_token,refreshToken:n.refresh_token||e,expiresIn:n.expires_in,tokenType:n.token_type||"Bearer"}}async logout(e){await fetch(`${this.baseUrl}/api/v1/auth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}async validateToken(e){try{return(await fetch(`${this.baseUrl}/api/v1/auth/validate`,{headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}}function s(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}async function o(e){const t=(new TextEncoder).encode(e);return function(e){const t=new Uint8Array(e),r=Array.from(t,e=>String.fromCharCode(e)).join("");return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}(await crypto.subtle.digest("SHA-256",t))}class i{constructor(e){this.tokenExpiryTimer=null;const o=function(e){return e?Array.isArray(e)?e:"string"==typeof e?e.split(" "):["openid","profile","email"]:["openid","profile","email"]}(e.scope);this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:o,responseType:e.responseType||"code",state:e.state||s(),usePKCE:!1!==e.usePKCE,codeChallengeMethod:e.codeChallengeMethod||"S256",grantType:e.grantType||"authorization_code"},this.storage=new t,this.api=new r(this.config.apiUrl),this.listeners=new Map,"undefined"!=typeof window&&(this.handleCallback(),this.setupTokenExpiryMonitoring())}async login(e){const t=this.config.state;this.storage.setState("oauth_state",t);const r={response_type:this.config.responseType,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:Array.isArray(this.config.scope)?this.config.scope.join(" "):this.config.scope,state:t,...e};if(this.config.usePKCE){const e=function(){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=Math.floor(86*Math.random())+43;let r="";for(let s=0;s<t;s++)r+=e.charAt(Math.floor(66*Math.random()));return r}(),t=await o(e);this.storage.setCodeVerifier(e),this.storage.setCodeChallenge(t),r.code_challenge=t,r.code_challenge_method=this.config.codeChallengeMethod}const s=`${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(r)}`;window.location.href=s}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.clearTokenExpiryTimer(),this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,r)=>{t[r]=e}),t}(window.location.href),t=e.error;if(t){const r=e.error_description||t;throw this.emit("error",{error:t,error_description:r}),new Error(r)}const r=e.code;if(!r)return;if(e.state!==this.storage.getState("oauth_state"))throw this.emit("error",{error:"invalid_state",error_description:"State parameter mismatch"}),new Error("Invalid state parameter");this.storage.removeState("oauth_state");try{const e=this.config.usePKCE&&this.storage.getCodeVerifier()||void 0,t=await this.api.exchangeCodeForTokens(r,this.config.clientId,this.config.redirectUri,e,this.config.clientSecret);this.storage.setTokens(t);const s=await this.api.getUserInfo(t.accessToken);this.storage.setUser(s),this.config.usePKCE&&(this.storage.removeCodeVerifier(),this.storage.removeCodeChallenge()),this.setupTokenExpiryMonitoring(),this.emit("login",{user:s,tokens:t}),window.history.replaceState({},document.title,window.location.pathname+window.location.hash)}catch(t){throw this.emit("error",t),t}}getUser(){return this.storage.getUser()}getAccessToken(){return this.storage.getTokens()?.accessToken||null}getRefreshToken(){return this.storage.getTokens()?.refreshToken||null}isAuthenticated(){return this.storage.hasValidToken()&&!!this.storage.getUser()}getState(){return{isAuthenticated:this.isAuthenticated(),user:this.getUser(),tokens:this.storage.getTokens()}}async validateToken(){const e=this.getAccessToken();if(!e)return!1;try{return await this.api.validateToken(e)}catch{return!1}}setupTokenExpiryMonitoring(){this.clearTokenExpiryTimer();const e=this.storage.getTokens(),t=this.storage.getTokenIssuedAt();if(!e||!t)return;const r=1e3*(e.expiresIn-300);r>0&&(this.tokenExpiryTimer=setTimeout(async()=>{try{await this.refreshToken()}catch(e){this.emit("token-expired",e),await this.logout()}},r))}clearTokenExpiryTimer(){this.tokenExpiryTimer&&(clearTimeout(this.tokenExpiryTimer),this.tokenExpiryTimer=null)}async refreshToken(){const e=this.storage.getTokens();if(!e?.refreshToken)throw new Error("No refresh token available");try{const t=await this.api.refreshAccessToken(e.refreshToken,this.config.clientId,this.config.clientSecret);this.storage.setTokens(t),this.setupTokenExpiryMonitoring(),this.emit("token-refresh",t)}catch(e){throw this.emit("error",e),e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){const r={type:e,data:t};this.listeners.get(e)?.forEach(e=>e(r))}clearStorage(){this.storage.clear()}destroy(){this.clearTokenExpiryTimer(),this.listeners.clear()}}export{i as Custos};
2
2
  //# sourceMappingURL=index.esm.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.esm.js","sources":["../src/storage.ts","../src/api.ts","../src/Custos.ts","../src/utils.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\r\n\r\nconst STORAGE_PREFIX = 'custos_';\r\n\r\nexport class Storage {\r\n\tprivate storage: globalThis.Storage;\r\n\r\n\tconstructor(useSessionStorage = false) {\r\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\r\n\t}\r\n\r\n\tsetTokens(tokens: AuthTokens): void {\r\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\r\n\t}\r\n\r\n\tgetTokens(): AuthTokens | null {\r\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\r\n\t\treturn data ? JSON.parse(data) : null;\r\n\t}\r\n\r\n\tsetUser(user: User): void {\r\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\r\n\t}\r\n\r\n\tgetUser(): User | null {\r\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\r\n\t\treturn data ? JSON.parse(data) : null;\r\n\t}\r\n\r\n\tclear(): void {\r\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\r\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\r\n\t}\r\n\r\n\tsetState(key: string, value: string): void {\r\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\r\n\t}\r\n\r\n\tgetState(key: string): string | null {\r\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\r\n\t}\r\n\r\n\tremoveState(key: string): void {\r\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\r\n\t}\r\n}","import { AuthTokens, User } from './types';\r\n\r\nexport class ApiClient {\r\n\tprivate baseUrl: string;\r\n\r\n\tconstructor(baseUrl: string) {\r\n\t\tthis.baseUrl = baseUrl;\r\n\t}\r\n\r\n\tasync exchangeCodeForTokens(\r\n\t\tcode: string,\r\n\t\tclientId: string,\r\n\t\tredirectUri: string,\r\n\t\tclientSecret?: string\r\n\t): Promise<AuthTokens> {\r\n\t\tconst response = await fetch(`${this.baseUrl}/oauth/token`, {\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\t'Content-Type': 'application/json',\r\n\t\t\t},\r\n\t\t\tbody: JSON.stringify({\r\n\t\t\t\tgrant_type: 'authorization_code',\r\n\t\t\t\tcode,\r\n\t\t\t\tclient_id: clientId,\r\n\t\t\t\tclient_secret: clientSecret,\r\n\t\t\t\tredirect_uri: redirectUri,\r\n\t\t\t}),\r\n\t\t});\r\n\r\n\t\tif (!response.ok) {\r\n\t\t\tthrow new Error('Failed to exchange code for tokens');\r\n\t\t}\r\n\r\n\t\tconst data = await response.json();\r\n\t\treturn {\r\n\t\t\taccessToken: data.access_token,\r\n\t\t\trefreshToken: data.refresh_token,\r\n\t\t\texpiresIn: data.expires_in,\r\n\t\t\ttokenType: data.token_type,\r\n\t\t};\r\n\t}\r\n\r\n\tasync getUserInfo(accessToken: string): Promise<User> {\r\n\t\tconst response = await fetch(`${this.baseUrl}/oauth/userinfo`, {\r\n\t\t\theaders: {\r\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\r\n\t\t\t},\r\n\t\t});\r\n\r\n\t\tif (!response.ok) {\r\n\t\t\tthrow new Error('Failed to get user info');\r\n\t\t}\r\n\r\n\t\treturn response.json();\r\n\t}\r\n\r\n\tasync refreshAccessToken(\r\n\t\trefreshToken: string,\r\n\t\tclientId: string,\r\n\t\tclientSecret?: string\r\n\t): Promise<AuthTokens> {\r\n\t\tconst response = await fetch(`${this.baseUrl}/oauth/token`, {\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\t'Content-Type': 'application/json',\r\n\t\t\t},\r\n\t\t\tbody: JSON.stringify({\r\n\t\t\t\tgrant_type: 'refresh_token',\r\n\t\t\t\trefresh_token: refreshToken,\r\n\t\t\t\tclient_id: clientId,\r\n\t\t\t\tclient_secret: clientSecret,\r\n\t\t\t}),\r\n\t\t});\r\n\r\n\t\tif (!response.ok) {\r\n\t\t\tthrow new Error('Failed to refresh token');\r\n\t\t}\r\n\r\n\t\tconst data = await response.json();\r\n\t\treturn {\r\n\t\t\taccessToken: data.access_token,\r\n\t\t\trefreshToken: data.refresh_token,\r\n\t\t\texpiresIn: data.expires_in,\r\n\t\t\ttokenType: data.token_type,\r\n\t\t};\r\n\t}\r\n\r\n\tasync logout(accessToken: string): Promise<void> {\r\n\t\tawait fetch(`${this.baseUrl}/oauth/revoke`, {\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\r\n\t\t\t},\r\n\t\t});\r\n\t}\r\n}","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\r\nimport { Storage } from './storage';\r\nimport { ApiClient } from './api';\r\nimport { generateState, parseQueryString, isTokenExpired } from './utils';\r\n\r\nexport class Custos {\r\n\tprivate config: Required<CustosConfig>;\r\n\tprivate storage: Storage;\r\n\tprivate api: ApiClient;\r\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\r\n\tprivate tokenIssuedAt: number | null = null;\r\n\r\n\tconstructor(config: CustosConfig) {\r\n\t\tthis.config = {\r\n\t\t\tclientId: config.clientId,\r\n\t\t\tclientSecret: config.clientSecret || '',\r\n\t\t\tredirectUri: config.redirectUri,\r\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\r\n\t\t\tscope: config.scope || ['openid', 'profile', 'email'],\r\n\t\t};\r\n\r\n\t\tthis.storage = new Storage();\r\n\t\tthis.api = new ApiClient(this.config.apiUrl);\r\n\t\tthis.listeners = new Map();\r\n\r\n\t\t// Handle callback automatically\r\n\t\tthis.handleCallback();\r\n\r\n\t\t// Setup token refresh\r\n\t\tthis.setupTokenRefresh();\r\n\t}\r\n\r\n\t// Authentication Methods\r\n\tasync login(): Promise<void> {\r\n\t\tconst state = generateState();\r\n\t\tthis.storage.setState('oauth_state', state);\r\n\r\n\t\tconst params = new URLSearchParams({\r\n\t\t\tresponse_type: 'code',\r\n\t\t\tclient_id: this.config.clientId,\r\n\t\t\tredirect_uri: this.config.redirectUri,\r\n\t\t\tscope: this.config.scope.join(' '),\r\n\t\t\tstate,\r\n\t\t});\r\n\r\n\t\twindow.location.href = `${this.config.apiUrl}/oauth/authorize?${params}`;\r\n\t}\r\n\r\n\tasync logout(): Promise<void> {\r\n\t\tconst tokens = this.storage.getTokens();\r\n\r\n\t\tif (tokens?.accessToken) {\r\n\t\t\ttry {\r\n\t\t\t\tawait this.api.logout(tokens.accessToken);\r\n\t\t\t} catch (error) {\r\n\t\t\t\tconsole.error('Logout error:', error);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.storage.clear();\r\n\t\tthis.emit('logout', null);\r\n\t}\r\n\r\n\tasync handleCallback(): Promise<void> {\r\n\t\tconst params = parseQueryString(window.location.href);\r\n\t\tconst code = params.code;\r\n\t\tconst state = params.state;\r\n\r\n\t\tif (!code) return;\r\n\r\n\t\tconst savedState = this.storage.getState('oauth_state');\r\n\t\tif (state !== savedState) {\r\n\t\t\tthrow new Error('Invalid state parameter');\r\n\t\t}\r\n\r\n\t\tthis.storage.removeState('oauth_state');\r\n\r\n\t\ttry {\r\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\r\n\t\t\t\tcode,\r\n\t\t\t\tthis.config.clientId,\r\n\t\t\t\tthis.config.redirectUri,\r\n\t\t\t\tthis.config.clientSecret\r\n\t\t\t);\r\n\r\n\t\t\tthis.tokenIssuedAt = Date.now();\r\n\t\t\tthis.storage.setTokens(tokens);\r\n\r\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\r\n\t\t\tthis.storage.setUser(user);\r\n\r\n\t\t\tthis.emit('login', { user, tokens });\r\n\r\n\t\t\t// Clean URL\r\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname);\r\n\t\t} catch (error) {\r\n\t\t\tthis.emit('error', error);\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t// User Methods\r\n\tgetUser(): User | null {\r\n\t\treturn this.storage.getUser();\r\n\t}\r\n\r\n\tgetAccessToken(): string | null {\r\n\t\treturn this.storage.getTokens()?.accessToken || null;\r\n\t}\r\n\r\n\tisAuthenticated(): boolean {\r\n\t\treturn !!this.storage.getTokens() && !!this.storage.getUser();\r\n\t}\r\n\r\n\tgetState(): AuthState {\r\n\t\treturn {\r\n\t\t\tisAuthenticated: this.isAuthenticated(),\r\n\t\t\tuser: this.getUser(),\r\n\t\t\ttokens: this.storage.getTokens(),\r\n\t\t};\r\n\t}\r\n\r\n\t// Token Refresh\r\n\tprivate setupTokenRefresh(): void {\r\n\t\tsetInterval(async () => {\r\n\t\t\tif (this.shouldRefreshToken()) {\r\n\t\t\t\tawait this.refreshToken();\r\n\t\t\t}\r\n\t\t}, 60000); // Check every minute\r\n\t}\r\n\r\n\tprivate shouldRefreshToken(): boolean {\r\n\t\tconst tokens = this.storage.getTokens();\r\n\t\tif (!tokens || !this.tokenIssuedAt) return false;\r\n\r\n\t\treturn isTokenExpired(tokens.expiresIn, this.tokenIssuedAt);\r\n\t}\r\n\r\n\tasync refreshToken(): Promise<void> {\r\n\t\tconst tokens = this.storage.getTokens();\r\n\t\tif (!tokens?.refreshToken) {\r\n\t\t\tthrow new Error('No refresh token available');\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\r\n\t\t\t\ttokens.refreshToken,\r\n\t\t\t\tthis.config.clientId,\r\n\t\t\t\tthis.config.clientSecret\r\n\t\t\t);\r\n\r\n\t\t\tthis.tokenIssuedAt = Date.now();\r\n\t\t\tthis.storage.setTokens(newTokens);\r\n\r\n\t\t\tthis.emit('token-refresh', newTokens);\r\n\t\t} catch (error) {\r\n\t\t\tthis.emit('error', error);\r\n\t\t\t// If refresh fails, logout\r\n\t\t\tawait this.logout();\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t// Event Handling\r\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\r\n\t\tif (!this.listeners.has(event)) {\r\n\t\t\tthis.listeners.set(event, new Set());\r\n\t\t}\r\n\t\tthis.listeners.get(event)!.add(callback);\r\n\t}\r\n\r\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\r\n\t\tthis.listeners.get(event)?.delete(callback);\r\n\t}\r\n\r\n\tprivate emit(type: AuthEventType, data?: any): void {\r\n\t\tconst event: AuthEvent = { type, data };\r\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\r\n\t}\r\n}","export function generateState(): string {\r\n\tconst array = new Uint8Array(32);\r\n\tcrypto.getRandomValues(array);\r\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\r\n}\r\n\r\nexport function parseQueryString(url: string): Record<string, string> {\r\n\tconst params: Record<string, string> = {};\r\n\tconst searchParams = new URL(url).searchParams;\r\n\tsearchParams.forEach((value, key) => {\r\n\t\tparams[key] = value;\r\n\t});\r\n\treturn params;\r\n}\r\n\r\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\r\n\tconst now = Date.now();\r\n\tconst expirationTime = issuedAt + expiresIn * 1000;\r\n\t// Refresh 5 minutes before expiration\r\n\treturn now >= expirationTime - 5 * 60 * 1000;\r\n}"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","getTokens","data","getItem","parse","setUser","user","getUser","clear","removeItem","setState","key","value","getState","removeState","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","response","fetch","method","headers","body","grant_type","client_id","client_secret","redirect_uri","ok","Error","json","accessToken","access_token","refreshToken","refresh_token","expiresIn","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","Custos","config","tokenIssuedAt","apiUrl","scope","api","listeners","Map","handleCallback","setupTokenRefresh","login","state","array","Uint8Array","crypto","getRandomValues","Array","from","byte","toString","padStart","join","generateState","params","URLSearchParams","response_type","window","location","href","error","console","emit","url","URL","searchParams","forEach","parseQueryString","Date","now","history","replaceState","document","title","pathname","getAccessToken","isAuthenticated","setInterval","async","shouldRefreshToken","issuedAt","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type"],"mappings":"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"}
1
+ {"version":3,"file":"index.esm.js","sources":["../src/storage.ts","../src/api.ts","../src/utils.ts","../src/Custos.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\n\nconst STORAGE_PREFIX = 'custos_';\n\nexport class Storage {\n\tprivate storage: globalThis.Storage;\n\n\tconstructor(useSessionStorage = false) {\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\n\t}\n\n\t// Tokens\n\tsetTokens(tokens: AuthTokens): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());\n\t}\n\n\tgetTokens(): AuthTokens | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\tgetTokenIssuedAt(): number | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\treturn data ? parseInt(data, 10) : null;\n\t}\n\n\t// User\n\tsetUser(user: User): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\n\t}\n\n\tgetUser(): User | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\t// State & PKCE\n\tsetState(key: string, value: string): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\n\t}\n\n\tgetState(key: string): string | null {\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\tremoveState(key: string): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\t// PKCE specific\n\tsetCodeVerifier(codeVerifier: string): void {\n\t\tthis.setState('code_verifier', codeVerifier);\n\t}\n\n\tgetCodeVerifier(): string | null {\n\t\treturn this.getState('code_verifier');\n\t}\n\n\tremoveCodeVerifier(): void {\n\t\tthis.removeState('code_verifier');\n\t}\n\n\tsetCodeChallenge(codeChallenge: string): void {\n\t\tthis.setState('code_challenge', codeChallenge);\n\t}\n\n\tgetCodeChallenge(): string | null {\n\t\treturn this.getState('code_challenge');\n\t}\n\n\tremoveCodeChallenge(): void {\n\t\tthis.removeState('code_challenge');\n\t}\n\n\t// Clear all\n\tclear(): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\n\t\tthis.removeState('oauth_state');\n\t\tthis.removeCodeVerifier();\n\t\tthis.removeCodeChallenge();\n\t}\n\n\t// Validation\n\thasValidToken(): boolean {\n\t\tconst tokens = this.getTokens();\n\t\tconst issuedAt = this.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return false;\n\n\t\tconst now = Date.now();\n\t\tconst expirationTime = issuedAt + tokens.expiresIn * 1000;\n\n\t\treturn now < expirationTime;\n\t}\n}\n","import { AuthTokens, User } from './types';\n\nexport class ApiClient {\n\tprivate baseUrl: string;\n\n\tconstructor(baseUrl: string) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tasync exchangeCodeForTokens(\n\t\tcode: string,\n\t\tclientId: string,\n\t\tredirectUri: string,\n\t\tcodeVerifier?: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t};\n\n\t\t// Add PKCE code_verifier if present\n\t\tif (codeVerifier) {\n\t\t\tbody.code_verifier = codeVerifier;\n\t\t}\n\n\t\t// Add client_secret if present (for confidential clients)\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({\n\t\t\t\terror: 'unknown_error',\n\t\t\t\terror_description: 'Failed to exchange code for tokens'\n\t\t\t}));\n\n\t\t\tthrow new Error(errorData.error_description || errorData.error || 'Token exchange failed');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result; // Support both {data: {...}} and direct response\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token,\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync getUserInfo(accessToken: string): Promise<User> {\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/system/users/profile`, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to get user info');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\treturn result.data || result;\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t\tclientId: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: clientId,\n\t\t};\n\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to refresh token');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result;\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync logout(accessToken: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/api/v1/auth/revoke`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync validateToken(accessToken: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn response.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","export function generateState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseQueryString(url: string): Record<string, string> {\n\tconst params: Record<string, string> = {};\n\tconst searchParams = new URL(url).searchParams;\n\tsearchParams.forEach((value, key) => {\n\t\tparams[key] = value;\n\t});\n\treturn params;\n}\n\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\n\tconst now = Date.now();\n\tconst expirationTime = issuedAt + expiresIn * 1000;\n\t// Refresh 5 minutes before expiration\n\treturn now >= expirationTime - 5 * 60 * 1000;\n}\n\n// PKCE utilities\nexport function generateCodeVerifier(): string {\n\tconst charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\tconst length = Math.floor(Math.random() * 86) + 43; // 43-128 characters\n\tlet verifier = '';\n\tfor (let i = 0; i < length; i++) {\n\t\tverifier += charset.charAt(Math.floor(Math.random() * charset.length));\n\t}\n\treturn verifier;\n}\n\nexport async function generateCodeChallenge(codeVerifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(codeVerifier);\n\tconst digest = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(digest);\n}\n\nfunction base64UrlEncode(digest: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(digest);\n\tconst binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nexport function normalizeScope(scope?: string | string[]): string[] {\n\tif (!scope) return ['openid', 'profile', 'email'];\n\tif (Array.isArray(scope)) return scope;\n\tif (typeof scope === 'string') return scope.split(' ');\n\treturn ['openid', 'profile', 'email'];\n}\n","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\nimport { Storage } from './storage';\nimport { ApiClient } from './api';\nimport {\n\tgenerateState,\n\tparseQueryString,\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tnormalizeScope\n} from './utils';\n\nexport class Custos {\n\tprivate config: Required<CustosConfig>;\n\tprivate storage: Storage;\n\tprivate api: ApiClient;\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\n\tprivate tokenExpiryTimer: any = null;\n\n\tconstructor(config: CustosConfig) {\n\t\t// Normalize scope\n\t\tconst scope = normalizeScope(config.scope);\n\n\t\tthis.config = {\n\t\t\tclientId: config.clientId,\n\t\t\tclientSecret: config.clientSecret || '',\n\t\t\tredirectUri: config.redirectUri,\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\n\t\t\tscope,\n\t\t\tresponseType: config.responseType || 'code',\n\t\t\tstate: config.state || generateState(),\n\t\t\tusePKCE: config.usePKCE !== false, // Default to true\n\t\t\tcodeChallengeMethod: config.codeChallengeMethod || 'S256',\n\t\t\tgrantType: config.grantType || 'authorization_code',\n\t\t};\n\n\t\tthis.storage = new Storage();\n\t\tthis.api = new ApiClient(this.config.apiUrl);\n\t\tthis.listeners = new Map();\n\n\t\t// Handle callback automatically\n\t\tif (typeof window !== 'undefined') {\n\t\t\tthis.handleCallback();\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t}\n\t}\n\n\t// ==================== Authentication Methods ====================\n\n\tasync login(additionalParams?: Record<string, string>): Promise<void> {\n\t\tconst state = this.config.state;\n\t\tthis.storage.setState('oauth_state', state);\n\n\t\tconst params: Record<string, string> = {\n\t\t\tresponse_type: this.config.responseType,\n\t\t\tclient_id: this.config.clientId,\n\t\t\tredirect_uri: this.config.redirectUri,\n\t\t\tscope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,\n\t\t\tstate,\n\t\t\t...additionalParams,\n\t\t};\n\n\t\t// Add PKCE if enabled\n\t\tif (this.config.usePKCE) {\n\t\t\tconst codeVerifier = generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t\t\tthis.storage.setCodeVerifier(codeVerifier);\n\t\t\tthis.storage.setCodeChallenge(codeChallenge);\n\n\t\t\tparams.code_challenge = codeChallenge;\n\t\t\tparams.code_challenge_method = this.config.codeChallengeMethod;\n\t\t}\n\n\t\tconst authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;\n\t\twindow.location.href = authUrl;\n\t}\n\n\tasync logout(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\n\t\tif (tokens?.accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.api.logout(tokens.accessToken);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Logout error:', error);\n\t\t\t}\n\t\t}\n\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.storage.clear();\n\t\tthis.emit('logout', null);\n\t}\n\n\tasync handleCallback(): Promise<void> {\n\t\tconst params = parseQueryString(window.location.href);\n\n\t\t// Check for errors\n\t\tconst error = params.error;\n\t\tif (error) {\n\t\t\tconst errorDescription = params.error_description || error;\n\t\t\tthis.emit('error', { error, error_description: errorDescription });\n\t\t\tthrow new Error(errorDescription);\n\t\t}\n\n\t\t// Check for authorization code\n\t\tconst code = params.code;\n\t\tif (!code) return;\n\n\t\t// Validate state\n\t\tconst state = params.state;\n\t\tconst savedState = this.storage.getState('oauth_state');\n\t\tif (state !== savedState) {\n\t\t\tthis.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });\n\t\t\tthrow new Error('Invalid state parameter');\n\t\t}\n\n\t\tthis.storage.removeState('oauth_state');\n\n\t\ttry {\n\t\t\t// Get code_verifier if using PKCE\n\t\t\tconst codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;\n\n\t\t\t// Exchange code for tokens\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\n\t\t\t\tcode,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.redirectUri,\n\t\t\t\tcodeVerifier,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(tokens);\n\n\t\t\t// Get user info\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\n\t\t\tthis.storage.setUser(user);\n\n\t\t\t// Clean up PKCE data\n\t\t\tif (this.config.usePKCE) {\n\t\t\t\tthis.storage.removeCodeVerifier();\n\t\t\t\tthis.storage.removeCodeChallenge();\n\t\t\t}\n\n\t\t\t// Setup token expiry monitoring\n\t\t\tthis.setupTokenExpiryMonitoring();\n\n\t\t\tthis.emit('login', { user, tokens });\n\n\t\t\t// Clean URL (remove query params)\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname + window.location.hash);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== User Methods ====================\n\n\tgetUser(): User | null {\n\t\treturn this.storage.getUser();\n\t}\n\n\tgetAccessToken(): string | null {\n\t\treturn this.storage.getTokens()?.accessToken || null;\n\t}\n\n\tgetRefreshToken(): string | null {\n\t\treturn this.storage.getTokens()?.refreshToken || null;\n\t}\n\n\tisAuthenticated(): boolean {\n\t\treturn this.storage.hasValidToken() && !!this.storage.getUser();\n\t}\n\n\tgetState(): AuthState {\n\t\treturn {\n\t\t\tisAuthenticated: this.isAuthenticated(),\n\t\t\tuser: this.getUser(),\n\t\t\ttokens: this.storage.getTokens(),\n\t\t};\n\t}\n\n\tasync validateToken(): Promise<boolean> {\n\t\tconst accessToken = this.getAccessToken();\n\t\tif (!accessToken) return false;\n\n\t\ttry {\n\t\t\treturn await this.api.validateToken(accessToken);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==================== Token Refresh ====================\n\n\tprivate setupTokenExpiryMonitoring(): void {\n\t\tthis.clearTokenExpiryTimer();\n\n\t\tconst tokens = this.storage.getTokens();\n\t\tconst issuedAt = this.storage.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return;\n\n\t\t// Refresh 5 minutes before expiry\n\t\tconst timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer\n\n\t\tif (timeUntilRefresh > 0) {\n\t\t\tthis.tokenExpiryTimer = setTimeout(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.refreshToken();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.emit('token-expired', error);\n\t\t\t\t\tawait this.logout();\n\t\t\t\t}\n\t\t\t}, timeUntilRefresh);\n\t\t}\n\t}\n\n\tprivate clearTokenExpiryTimer(): void {\n\t\tif (this.tokenExpiryTimer) {\n\t\t\tclearTimeout(this.tokenExpiryTimer);\n\t\t\tthis.tokenExpiryTimer = null;\n\t\t}\n\t}\n\n\tasync refreshToken(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\t\tif (!tokens?.refreshToken) {\n\t\t\tthrow new Error('No refresh token available');\n\t\t}\n\n\t\ttry {\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\n\t\t\t\ttokens.refreshToken,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(newTokens);\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t\tthis.emit('token-refresh', newTokens);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== Event Handling ====================\n\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)!.add(callback);\n\t}\n\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tthis.listeners.get(event)?.delete(callback);\n\t}\n\n\tprivate emit(type: AuthEventType, data?: any): void {\n\t\tconst event: AuthEvent = { type, data };\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\n\t}\n\n\t// ==================== Utility Methods ====================\n\n\tclearStorage(): void {\n\t\tthis.storage.clear();\n\t}\n\n\tdestroy(): void {\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.listeners.clear();\n\t}\n}\n"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","Date","now","toString","getTokens","data","getItem","parse","getTokenIssuedAt","parseInt","setUser","user","getUser","setState","key","value","getState","removeState","removeItem","setCodeVerifier","codeVerifier","getCodeVerifier","removeCodeVerifier","setCodeChallenge","codeChallenge","getCodeChallenge","removeCodeChallenge","clear","hasValidToken","issuedAt","expiresIn","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","body","grant_type","client_id","redirect_uri","code_verifier","client_secret","response","fetch","method","headers","URLSearchParams","ok","errorData","json","catch","error","error_description","Error","result","accessToken","access_token","refreshToken","refresh_token","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","validateToken","generateState","array","Uint8Array","crypto","getRandomValues","Array","from","byte","padStart","join","async","generateCodeChallenge","TextEncoder","encode","digest","bytes","binary","String","fromCharCode","btoa","replace","base64UrlEncode","subtle","Custos","config","tokenExpiryTimer","scope","isArray","split","normalizeScope","apiUrl","responseType","state","usePKCE","codeChallengeMethod","grantType","api","listeners","Map","window","handleCallback","setupTokenExpiryMonitoring","login","additionalParams","params","response_type","charset","length","Math","floor","random","verifier","i","charAt","generateCodeVerifier","code_challenge","code_challenge_method","authUrl","location","href","console","clearTokenExpiryTimer","emit","url","URL","searchParams","forEach","parseQueryString","errorDescription","undefined","history","replaceState","document","title","pathname","hash","getAccessToken","getRefreshToken","isAuthenticated","timeUntilRefresh","setTimeout","clearTimeout","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type","clearStorage","destroy"],"mappings":"AAEA,MAAMA,EAAiB,gBAEVC,EAGZ,WAAAC,CAAYC,GAAoB,GAC/BC,KAAKC,QAAUF,EAAoBG,eAAiBC,YACpD,CAGD,SAAAC,CAAUC,GACTL,KAAKC,QAAQK,QAAQ,GAAGV,UAAwBW,KAAKC,UAAUH,IAC/DL,KAAKC,QAAQK,QAAQ,GAAGV,mBAAiCa,KAAKC,MAAMC,WACpE,CAED,SAAAC,GACC,MAAMC,EAAOb,KAAKC,QAAQa,QAAQ,GAAGlB,WACrC,OAAOiB,EAAON,KAAKQ,MAAMF,GAAQ,IACjC,CAED,gBAAAG,GACC,MAAMH,EAAOb,KAAKC,QAAQa,QAAQ,GAAGlB,oBACrC,OAAOiB,EAAOI,SAASJ,EAAM,IAAM,IACnC,CAGD,OAAAK,CAAQC,GACPnB,KAAKC,QAAQK,QAAQ,GAAGV,QAAsBW,KAAKC,UAAUW,GAC7D,CAED,OAAAC,GACC,MAAMP,EAAOb,KAAKC,QAAQa,QAAQ,GAAGlB,SACrC,OAAOiB,EAAON,KAAKQ,MAAMF,GAAQ,IACjC,CAGD,QAAAQ,CAASC,EAAaC,GACrBvB,KAAKC,QAAQK,QAAQ,GAAGV,IAAiB0B,IAAOC,EAChD,CAED,QAAAC,CAASF,GACR,OAAOtB,KAAKC,QAAQa,QAAQ,GAAGlB,IAAiB0B,IAChD,CAED,WAAAG,CAAYH,GACXtB,KAAKC,QAAQyB,WAAW,GAAG9B,IAAiB0B,IAC5C,CAGD,eAAAK,CAAgBC,GACf5B,KAAKqB,SAAS,gBAAiBO,EAC/B,CAED,eAAAC,GACC,OAAO7B,KAAKwB,SAAS,gBACrB,CAED,kBAAAM,GACC9B,KAAKyB,YAAY,gBACjB,CAED,gBAAAM,CAAiBC,GAChBhC,KAAKqB,SAAS,iBAAkBW,EAChC,CAED,gBAAAC,GACC,OAAOjC,KAAKwB,SAAS,iBACrB,CAED,mBAAAU,GACClC,KAAKyB,YAAY,iBACjB,CAGD,KAAAU,GACCnC,KAAKC,QAAQyB,WAAW,GAAG9B,WAC3BI,KAAKC,QAAQyB,WAAW,GAAG9B,oBAC3BI,KAAKC,QAAQyB,WAAW,GAAG9B,SAC3BI,KAAKyB,YAAY,eACjBzB,KAAK8B,qBACL9B,KAAKkC,qBACL,CAGD,aAAAE,GACC,MAAM/B,EAASL,KAAKY,YACdyB,EAAWrC,KAAKgB,mBAEtB,IAAKX,IAAWgC,EAAU,OAAO,EAKjC,OAHY5B,KAAKC,MACM2B,EAA8B,IAAnBhC,EAAOiC,SAGzC,QC9FWC,EAGZ,WAAAzC,CAAY0C,GACXxC,KAAKwC,QAAUA,CACf,CAED,2BAAMC,CACLC,EACAC,EACAC,EACAhB,EACAiB,GAEA,MAAMC,EAA+B,CACpCC,WAAY,qBACZL,OACAM,UAAWL,EACXM,aAAcL,GAIXhB,IACHkB,EAAKI,cAAgBtB,GAIlBiB,IACHC,EAAKK,cAAgBN,GAGtB,MAAMO,QAAiBC,MAAM,GAAGrD,KAAKwC,4BAA6B,CACjEc,OAAQ,OACRC,QAAS,CACR,eAAgB,qCAEjBT,KAAM,IAAIU,gBAAgBV,GAAMnC,aAGjC,IAAKyC,EAASK,GAAI,CACjB,MAAMC,QAAkBN,EAASO,OAAOC,MAAM,KAAO,CACpDC,MAAO,gBACPC,kBAAmB,wCAGpB,MAAM,IAAIC,MAAML,EAAUI,mBAAqBJ,EAAUG,OAAS,wBAClE,CAED,MAAMG,QAAeZ,EAASO,OACxB9C,EAAOmD,EAAOnD,MAAQmD,EAE5B,MAAO,CACNC,YAAapD,EAAKqD,aAClBC,aAActD,EAAKuD,cACnB9B,UAAWzB,EAAKwD,WAChBC,UAAWzD,EAAK0D,YAAc,SAE/B,CAED,iBAAMC,CAAYP,GACjB,MAAMb,QAAiBC,MAAM,GAAGrD,KAAKwC,sCAAuC,CAC3Ee,QAAS,CACRkB,cAAe,UAAUR,OAI3B,IAAKb,EAASK,GACb,MAAM,IAAIM,MAAM,2BAGjB,MAAMC,QAAeZ,EAASO,OAC9B,OAAOK,EAAOnD,MAAQmD,CACtB,CAED,wBAAMU,CACLP,EACAxB,EACAE,GAEA,MAAMC,EAA+B,CACpCC,WAAY,gBACZqB,cAAeD,EACfnB,UAAWL,GAGRE,IACHC,EAAKK,cAAgBN,GAGtB,MAAMO,QAAiBC,MAAM,GAAGrD,KAAKwC,4BAA6B,CACjEc,OAAQ,OACRC,QAAS,CACR,eAAgB,qCAEjBT,KAAM,IAAIU,gBAAgBV,GAAMnC,aAGjC,IAAKyC,EAASK,GACb,MAAM,IAAIM,MAAM,2BAGjB,MAAMC,QAAeZ,EAASO,OACxB9C,EAAOmD,EAAOnD,MAAQmD,EAE5B,MAAO,CACNC,YAAapD,EAAKqD,aAClBC,aAActD,EAAKuD,eAAiBD,EACpC7B,UAAWzB,EAAKwD,WAChBC,UAAWzD,EAAK0D,YAAc,SAE/B,CAED,YAAMI,CAAOV,SACNZ,MAAM,GAAGrD,KAAKwC,6BAA8B,CACjDc,OAAQ,OACRC,QAAS,CACRkB,cAAe,UAAUR,MAG3B,CAED,mBAAMW,CAAcX,GACnB,IAMC,aALuBZ,MAAM,GAAGrD,KAAKwC,+BAAgC,CACpEe,QAAS,CACRkB,cAAe,UAAUR,QAGXR,EAChB,CAAC,MACD,OAAO,CACP,CACD,WCtIcoB,IACf,MAAMC,EAAQ,IAAIC,WAAW,IAE7B,OADAC,OAAOC,gBAAgBH,GAChBI,MAAMC,KAAKL,EAAQM,GAASA,EAAKzE,SAAS,IAAI0E,SAAS,EAAG,MAAMC,KAAK,GAC7E,CA6BOC,eAAeC,EAAsB5D,GAC3C,MACMf,GADU,IAAI4E,aACCC,OAAO9D,GAE5B,OAGD,SAAyB+D,GACxB,MAAMC,EAAQ,IAAIb,WAAWY,GACvBE,EAASX,MAAMC,KAAKS,EAAQR,GAASU,OAAOC,aAAaX,IAAOE,KAAK,IAC3E,OAAOU,KAAKH,GACVI,QAAQ,MAAO,KACfA,QAAQ,MAAO,KACfA,QAAQ,KAAM,GACjB,CAVQC,OADclB,OAAOmB,OAAOR,OAAO,UAAW9E,GAEtD,OC3BauF,EAOZ,WAAAtG,CAAYuG,GAFJrG,KAAgBsG,iBAAQ,KAI/B,MAAMC,ED6BF,SAAyBA,GAC9B,OAAKA,EACDrB,MAAMsB,QAAQD,GAAeA,EACZ,iBAAVA,EAA2BA,EAAME,MAAM,KAC3C,CAAC,SAAU,UAAW,SAHV,CAAC,SAAU,UAAW,QAI1C,CClCgBC,CAAeL,EAAOE,OAEpCvG,KAAKqG,OAAS,CACb1D,SAAU0D,EAAO1D,SACjBE,aAAcwD,EAAOxD,cAAgB,GACrCD,YAAayD,EAAOzD,YACpB+D,OAAQN,EAAOM,QAAU,6BACzBJ,QACAK,aAAcP,EAAOO,cAAgB,OACrCC,MAAOR,EAAOQ,OAAShC,IACvBiC,SAA4B,IAAnBT,EAAOS,QAChBC,oBAAqBV,EAAOU,qBAAuB,OACnDC,UAAWX,EAAOW,WAAa,sBAGhChH,KAAKC,QAAU,IAAIJ,EACnBG,KAAKiH,IAAM,IAAI1E,EAAUvC,KAAKqG,OAAOM,QACrC3G,KAAKkH,UAAY,IAAIC,IAGC,oBAAXC,SACVpH,KAAKqH,iBACLrH,KAAKsH,6BAEN,CAID,WAAMC,CAAMC,GACX,MAAMX,EAAQ7G,KAAKqG,OAAOQ,MAC1B7G,KAAKC,QAAQoB,SAAS,cAAewF,GAErC,MAAMY,EAAiC,CACtCC,cAAe1H,KAAKqG,OAAOO,aAC3B5D,UAAWhD,KAAKqG,OAAO1D,SACvBM,aAAcjD,KAAKqG,OAAOzD,YAC1B2D,MAAOrB,MAAMsB,QAAQxG,KAAKqG,OAAOE,OAASvG,KAAKqG,OAAOE,MAAMjB,KAAK,KAAOtF,KAAKqG,OAAOE,MACpFM,WACGW,GAIJ,GAAIxH,KAAKqG,OAAOS,QAAS,CACxB,MAAMlF,aDvCR,MAAM+F,EAAU,qEACVC,EAASC,KAAKC,MAAsB,GAAhBD,KAAKE,UAAiB,GAChD,IAAIC,EAAW,GACf,IAAK,IAAIC,EAAI,EAAGA,EAAIL,EAAQK,IAC3BD,GAAYL,EAAQO,OAAOL,KAAKC,MAAsBH,GAAhBE,KAAKE,WAE5C,OAAOC,CACR,CCgCwBG,GACfnG,QAAsBwD,EAAsB5D,GAElD5B,KAAKC,QAAQ0B,gBAAgBC,GAC7B5B,KAAKC,QAAQ8B,iBAAiBC,GAE9ByF,EAAOW,eAAiBpG,EACxByF,EAAOY,sBAAwBrI,KAAKqG,OAAOU,mBAC3C,CAED,MAAMuB,EAAU,GAAGtI,KAAKqG,OAAOM,4BAA4B,IAAInD,gBAAgBiE,KAC/EL,OAAOmB,SAASC,KAAOF,CACvB,CAED,YAAM3D,GACL,MAAMtE,EAASL,KAAKC,QAAQW,YAE5B,GAAIP,GAAQ4D,YACX,UACOjE,KAAKiH,IAAItC,OAAOtE,EAAO4D,YAC7B,CAAC,MAAOJ,GACR4E,QAAQ5E,MAAM,gBAAiBA,EAC/B,CAGF7D,KAAK0I,wBACL1I,KAAKC,QAAQkC,QACbnC,KAAK2I,KAAK,SAAU,KACpB,CAED,oBAAMtB,GACL,MAAMI,EDxFF,SAA2BmB,GAChC,MAAMnB,EAAiC,CAAA,EAKvC,OAJqB,IAAIoB,IAAID,GAAKE,aACrBC,QAAQ,CAACxH,EAAOD,KAC5BmG,EAAOnG,GAAOC,IAERkG,CACR,CCiFiBuB,CAAiB5B,OAAOmB,SAASC,MAG1C3E,EAAQ4D,EAAO5D,MACrB,GAAIA,EAAO,CACV,MAAMoF,EAAmBxB,EAAO3D,mBAAqBD,EAErD,MADA7D,KAAK2I,KAAK,QAAS,CAAE9E,QAAOC,kBAAmBmF,IACzC,IAAIlF,MAAMkF,EAChB,CAGD,MAAMvG,EAAO+E,EAAO/E,KACpB,IAAKA,EAAM,OAKX,GAFc+E,EAAOZ,QACF7G,KAAKC,QAAQuB,SAAS,eAGxC,MADAxB,KAAK2I,KAAK,QAAS,CAAE9E,MAAO,gBAAiBC,kBAAmB,6BAC1D,IAAIC,MAAM,2BAGjB/D,KAAKC,QAAQwB,YAAY,eAEzB,IAEC,MAAMG,EAAe5B,KAAKqG,OAAOS,SAAU9G,KAAKC,QAAQ4B,wBAAiCqH,EAGnF7I,QAAeL,KAAKiH,IAAIxE,sBAC7BC,EACA1C,KAAKqG,OAAO1D,SACZ3C,KAAKqG,OAAOzD,YACZhB,EACA5B,KAAKqG,OAAOxD,cAGb7C,KAAKC,QAAQG,UAAUC,GAGvB,MAAMc,QAAanB,KAAKiH,IAAIzC,YAAYnE,EAAO4D,aAC/CjE,KAAKC,QAAQiB,QAAQC,GAGjBnB,KAAKqG,OAAOS,UACf9G,KAAKC,QAAQ6B,qBACb9B,KAAKC,QAAQiC,uBAIdlC,KAAKsH,6BAELtH,KAAK2I,KAAK,QAAS,CAAExH,OAAMd,WAG3B+G,OAAO+B,QAAQC,aAAa,CAAE,EAAEC,SAASC,MAAOlC,OAAOmB,SAASgB,SAAWnC,OAAOmB,SAASiB,KAC3F,CAAC,MAAO3F,GAER,MADA7D,KAAK2I,KAAK,QAAS9E,GACbA,CACN,CACD,CAID,OAAAzC,GACC,OAAOpB,KAAKC,QAAQmB,SACpB,CAED,cAAAqI,GACC,OAAOzJ,KAAKC,QAAQW,aAAaqD,aAAe,IAChD,CAED,eAAAyF,GACC,OAAO1J,KAAKC,QAAQW,aAAauD,cAAgB,IACjD,CAED,eAAAwF,GACC,OAAO3J,KAAKC,QAAQmC,mBAAqBpC,KAAKC,QAAQmB,SACtD,CAED,QAAAI,GACC,MAAO,CACNmI,gBAAiB3J,KAAK2J,kBACtBxI,KAAMnB,KAAKoB,UACXf,OAAQL,KAAKC,QAAQW,YAEtB,CAED,mBAAMgE,GACL,MAAMX,EAAcjE,KAAKyJ,iBACzB,IAAKxF,EAAa,OAAO,EAEzB,IACC,aAAajE,KAAKiH,IAAIrC,cAAcX,EACpC,CAAC,MACD,OAAO,CACP,CACD,CAIO,0BAAAqD,GACPtH,KAAK0I,wBAEL,MAAMrI,EAASL,KAAKC,QAAQW,YACtByB,EAAWrC,KAAKC,QAAQe,mBAE9B,IAAKX,IAAWgC,EAAU,OAG1B,MAAMuH,EAA8C,KAA1BvJ,EAAOiC,UAAY,KAEzCsH,EAAmB,IACtB5J,KAAKsG,iBAAmBuD,WAAWtE,UAClC,UACOvF,KAAKmE,cACX,CAAC,MAAON,GACR7D,KAAK2I,KAAK,gBAAiB9E,SACrB7D,KAAK2E,QACX,GACCiF,GAEJ,CAEO,qBAAAlB,GACH1I,KAAKsG,mBACRwD,aAAa9J,KAAKsG,kBAClBtG,KAAKsG,iBAAmB,KAEzB,CAED,kBAAMnC,GACL,MAAM9D,EAASL,KAAKC,QAAQW,YAC5B,IAAKP,GAAQ8D,aACZ,MAAM,IAAIJ,MAAM,8BAGjB,IACC,MAAMgG,QAAkB/J,KAAKiH,IAAIvC,mBAChCrE,EAAO8D,aACPnE,KAAKqG,OAAO1D,SACZ3C,KAAKqG,OAAOxD,cAGb7C,KAAKC,QAAQG,UAAU2J,GACvB/J,KAAKsH,6BACLtH,KAAK2I,KAAK,gBAAiBoB,EAC3B,CAAC,MAAOlG,GAER,MADA7D,KAAK2I,KAAK,QAAS9E,GACbA,CACN,CACD,CAID,EAAAmG,CAAGC,EAAsBC,GACnBlK,KAAKkH,UAAUiD,IAAIF,IACvBjK,KAAKkH,UAAUkD,IAAIH,EAAO,IAAII,KAE/BrK,KAAKkH,UAAUoD,IAAIL,GAAQM,IAAIL,EAC/B,CAED,GAAAM,CAAIP,EAAsBC,GACzBlK,KAAKkH,UAAUoD,IAAIL,IAAQQ,OAAOP,EAClC,CAEO,IAAAvB,CAAK+B,EAAqB7J,GACjC,MAAMoJ,EAAmB,CAAES,OAAM7J,QACjCb,KAAKkH,UAAUoD,IAAII,IAAO3B,QAASmB,GAAaA,EAASD,GACzD,CAID,YAAAU,GACC3K,KAAKC,QAAQkC,OACb,CAED,OAAAyI,GACC5K,KAAK0I,wBACL1I,KAAKkH,UAAU/E,OACf"}
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { Custos } from './Custos';
package/dist/index.umd.js CHANGED
@@ -1,2 +1,2 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Custos={})}(this,function(e){"use strict";const t="custos_";class s{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(e){this.storage.setItem(`${t}tokens`,JSON.stringify(e))}getTokens(){const e=this.storage.getItem(`${t}tokens`);return e?JSON.parse(e):null}setUser(e){this.storage.setItem(`${t}user`,JSON.stringify(e))}getUser(){const e=this.storage.getItem(`${t}user`);return e?JSON.parse(e):null}clear(){this.storage.removeItem(`${t}tokens`),this.storage.removeItem(`${t}user`)}setState(e,s){this.storage.setItem(`${t}${e}`,s)}getState(e){return this.storage.getItem(`${t}${e}`)}removeState(e){this.storage.removeItem(`${t}${e}`)}}class o{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,s,o){const r=await fetch(`${this.baseUrl}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"authorization_code",code:e,client_id:t,client_secret:o,redirect_uri:s})});if(!r.ok)throw new Error("Failed to exchange code for tokens");const n=await r.json();return{accessToken:n.access_token,refreshToken:n.refresh_token,expiresIn:n.expires_in,tokenType:n.token_type}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/oauth/userinfo`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");return t.json()}async refreshAccessToken(e,t,s){const o=await fetch(`${this.baseUrl}/oauth/token`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({grant_type:"refresh_token",refresh_token:e,client_id:t,client_secret:s})});if(!o.ok)throw new Error("Failed to refresh token");const r=await o.json();return{accessToken:r.access_token,refreshToken:r.refresh_token,expiresIn:r.expires_in,tokenType:r.token_type}}async logout(e){await fetch(`${this.baseUrl}/oauth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}}e.Custos=class{constructor(e){this.tokenIssuedAt=null,this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:e.scope||["openid","profile","email"]},this.storage=new s,this.api=new o(this.config.apiUrl),this.listeners=new Map,this.handleCallback(),this.setupTokenRefresh()}async login(){const e=function(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}();this.storage.setState("oauth_state",e);const t=new URLSearchParams({response_type:"code",client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:this.config.scope.join(" "),state:e});window.location.href=`${this.config.apiUrl}/oauth/authorize?${t}`}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,s)=>{t[s]=e}),t}(window.location.href),t=e.code,s=e.state;if(!t)return;if(s!==this.storage.getState("oauth_state"))throw new Error("Invalid state parameter");this.storage.removeState("oauth_state");try{const e=await this.api.exchangeCodeForTokens(t,this.config.clientId,this.config.redirectUri,this.config.clientSecret);this.tokenIssuedAt=Date.now(),this.storage.setTokens(e);const s=await this.api.getUserInfo(e.accessToken);this.storage.setUser(s),this.emit("login",{user:s,tokens:e}),window.history.replaceState({},document.title,window.location.pathname)}catch(e){throw this.emit("error",e),e}}getUser(){return this.storage.getUser()}getAccessToken(){return this.storage.getTokens()?.accessToken||null}isAuthenticated(){return!!this.storage.getTokens()&&!!this.storage.getUser()}getState(){return{isAuthenticated:this.isAuthenticated(),user:this.getUser(),tokens:this.storage.getTokens()}}setupTokenRefresh(){setInterval(async()=>{this.shouldRefreshToken()&&await this.refreshToken()},6e4)}shouldRefreshToken(){const e=this.storage.getTokens();return!(!e||!this.tokenIssuedAt)&&(t=e.expiresIn,s=this.tokenIssuedAt,Date.now()>=s+1e3*t-3e5);var t,s}async refreshToken(){const e=this.storage.getTokens();if(!e?.refreshToken)throw new Error("No refresh token available");try{const t=await this.api.refreshAccessToken(e.refreshToken,this.config.clientId,this.config.clientSecret);this.tokenIssuedAt=Date.now(),this.storage.setTokens(t),this.emit("token-refresh",t)}catch(e){throw this.emit("error",e),await this.logout(),e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){const s={type:e,data:t};this.listeners.get(e)?.forEach(e=>e(s))}},Object.defineProperty(e,"__esModule",{value:!0})});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Custos={})}(this,function(e){"use strict";const t="custos_";class r{constructor(e=!1){this.storage=e?sessionStorage:localStorage}setTokens(e){this.storage.setItem(`${t}tokens`,JSON.stringify(e)),this.storage.setItem(`${t}token_issued_at`,Date.now().toString())}getTokens(){const e=this.storage.getItem(`${t}tokens`);return e?JSON.parse(e):null}getTokenIssuedAt(){const e=this.storage.getItem(`${t}token_issued_at`);return e?parseInt(e,10):null}setUser(e){this.storage.setItem(`${t}user`,JSON.stringify(e))}getUser(){const e=this.storage.getItem(`${t}user`);return e?JSON.parse(e):null}setState(e,r){this.storage.setItem(`${t}${e}`,r)}getState(e){return this.storage.getItem(`${t}${e}`)}removeState(e){this.storage.removeItem(`${t}${e}`)}setCodeVerifier(e){this.setState("code_verifier",e)}getCodeVerifier(){return this.getState("code_verifier")}removeCodeVerifier(){this.removeState("code_verifier")}setCodeChallenge(e){this.setState("code_challenge",e)}getCodeChallenge(){return this.getState("code_challenge")}removeCodeChallenge(){this.removeState("code_challenge")}clear(){this.storage.removeItem(`${t}tokens`),this.storage.removeItem(`${t}token_issued_at`),this.storage.removeItem(`${t}user`),this.removeState("oauth_state"),this.removeCodeVerifier(),this.removeCodeChallenge()}hasValidToken(){const e=this.getTokens(),t=this.getTokenIssuedAt();if(!e||!t)return!1;return Date.now()<t+1e3*e.expiresIn}}class s{constructor(e){this.baseUrl=e}async exchangeCodeForTokens(e,t,r,s,o){const i={grant_type:"authorization_code",code:e,client_id:t,redirect_uri:r};s&&(i.code_verifier=s),o&&(i.client_secret=o);const n=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(i).toString()});if(!n.ok){const e=await n.json().catch(()=>({error:"unknown_error",error_description:"Failed to exchange code for tokens"}));throw new Error(e.error_description||e.error||"Token exchange failed")}const a=await n.json(),c=a.data||a;return{accessToken:c.access_token,refreshToken:c.refresh_token,expiresIn:c.expires_in,tokenType:c.token_type||"Bearer"}}async getUserInfo(e){const t=await fetch(`${this.baseUrl}/api/v1/system/users/profile`,{headers:{Authorization:`Bearer ${e}`}});if(!t.ok)throw new Error("Failed to get user info");const r=await t.json();return r.data||r}async refreshAccessToken(e,t,r){const s={grant_type:"refresh_token",refresh_token:e,client_id:t};r&&(s.client_secret=r);const o=await fetch(`${this.baseUrl}/api/v1/auth/token`,{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:new URLSearchParams(s).toString()});if(!o.ok)throw new Error("Failed to refresh token");const i=await o.json(),n=i.data||i;return{accessToken:n.access_token,refreshToken:n.refresh_token||e,expiresIn:n.expires_in,tokenType:n.token_type||"Bearer"}}async logout(e){await fetch(`${this.baseUrl}/api/v1/auth/revoke`,{method:"POST",headers:{Authorization:`Bearer ${e}`}})}async validateToken(e){try{return(await fetch(`${this.baseUrl}/api/v1/auth/validate`,{headers:{Authorization:`Bearer ${e}`}})).ok}catch{return!1}}}function o(){const e=new Uint8Array(32);return crypto.getRandomValues(e),Array.from(e,e=>e.toString(16).padStart(2,"0")).join("")}async function i(e){const t=(new TextEncoder).encode(e);return function(e){const t=new Uint8Array(e),r=Array.from(t,e=>String.fromCharCode(e)).join("");return btoa(r).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}(await crypto.subtle.digest("SHA-256",t))}e.Custos=class{constructor(e){this.tokenExpiryTimer=null;const t=function(e){return e?Array.isArray(e)?e:"string"==typeof e?e.split(" "):["openid","profile","email"]:["openid","profile","email"]}(e.scope);this.config={clientId:e.clientId,clientSecret:e.clientSecret||"",redirectUri:e.redirectUri,apiUrl:e.apiUrl||"https://custos.alimzen.com",scope:t,responseType:e.responseType||"code",state:e.state||o(),usePKCE:!1!==e.usePKCE,codeChallengeMethod:e.codeChallengeMethod||"S256",grantType:e.grantType||"authorization_code"},this.storage=new r,this.api=new s(this.config.apiUrl),this.listeners=new Map,"undefined"!=typeof window&&(this.handleCallback(),this.setupTokenExpiryMonitoring())}async login(e){const t=this.config.state;this.storage.setState("oauth_state",t);const r={response_type:this.config.responseType,client_id:this.config.clientId,redirect_uri:this.config.redirectUri,scope:Array.isArray(this.config.scope)?this.config.scope.join(" "):this.config.scope,state:t,...e};if(this.config.usePKCE){const e=function(){const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~",t=Math.floor(86*Math.random())+43;let r="";for(let s=0;s<t;s++)r+=e.charAt(Math.floor(66*Math.random()));return r}(),t=await i(e);this.storage.setCodeVerifier(e),this.storage.setCodeChallenge(t),r.code_challenge=t,r.code_challenge_method=this.config.codeChallengeMethod}const s=`${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(r)}`;window.location.href=s}async logout(){const e=this.storage.getTokens();if(e?.accessToken)try{await this.api.logout(e.accessToken)}catch(e){console.error("Logout error:",e)}this.clearTokenExpiryTimer(),this.storage.clear(),this.emit("logout",null)}async handleCallback(){const e=function(e){const t={};return new URL(e).searchParams.forEach((e,r)=>{t[r]=e}),t}(window.location.href),t=e.error;if(t){const r=e.error_description||t;throw this.emit("error",{error:t,error_description:r}),new Error(r)}const r=e.code;if(!r)return;if(e.state!==this.storage.getState("oauth_state"))throw this.emit("error",{error:"invalid_state",error_description:"State parameter mismatch"}),new Error("Invalid state parameter");this.storage.removeState("oauth_state");try{const e=this.config.usePKCE&&this.storage.getCodeVerifier()||void 0,t=await this.api.exchangeCodeForTokens(r,this.config.clientId,this.config.redirectUri,e,this.config.clientSecret);this.storage.setTokens(t);const s=await this.api.getUserInfo(t.accessToken);this.storage.setUser(s),this.config.usePKCE&&(this.storage.removeCodeVerifier(),this.storage.removeCodeChallenge()),this.setupTokenExpiryMonitoring(),this.emit("login",{user:s,tokens:t}),window.history.replaceState({},document.title,window.location.pathname+window.location.hash)}catch(t){throw this.emit("error",t),t}}getUser(){return this.storage.getUser()}getAccessToken(){return this.storage.getTokens()?.accessToken||null}getRefreshToken(){return this.storage.getTokens()?.refreshToken||null}isAuthenticated(){return this.storage.hasValidToken()&&!!this.storage.getUser()}getState(){return{isAuthenticated:this.isAuthenticated(),user:this.getUser(),tokens:this.storage.getTokens()}}async validateToken(){const e=this.getAccessToken();if(!e)return!1;try{return await this.api.validateToken(e)}catch{return!1}}setupTokenExpiryMonitoring(){this.clearTokenExpiryTimer();const e=this.storage.getTokens(),t=this.storage.getTokenIssuedAt();if(!e||!t)return;const r=1e3*(e.expiresIn-300);r>0&&(this.tokenExpiryTimer=setTimeout(async()=>{try{await this.refreshToken()}catch(e){this.emit("token-expired",e),await this.logout()}},r))}clearTokenExpiryTimer(){this.tokenExpiryTimer&&(clearTimeout(this.tokenExpiryTimer),this.tokenExpiryTimer=null)}async refreshToken(){const e=this.storage.getTokens();if(!e?.refreshToken)throw new Error("No refresh token available");try{const t=await this.api.refreshAccessToken(e.refreshToken,this.config.clientId,this.config.clientSecret);this.storage.setTokens(t),this.setupTokenExpiryMonitoring(),this.emit("token-refresh",t)}catch(e){throw this.emit("error",e),e}}on(e,t){this.listeners.has(e)||this.listeners.set(e,new Set),this.listeners.get(e).add(t)}off(e,t){this.listeners.get(e)?.delete(t)}emit(e,t){const r={type:e,data:t};this.listeners.get(e)?.forEach(e=>e(r))}clearStorage(){this.storage.clear()}destroy(){this.clearTokenExpiryTimer(),this.listeners.clear()}},Object.defineProperty(e,"__esModule",{value:!0})});
2
2
  //# sourceMappingURL=index.umd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.umd.js","sources":["../src/storage.ts","../src/api.ts","../src/Custos.ts","../src/utils.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\r\n\r\nconst STORAGE_PREFIX = 'custos_';\r\n\r\nexport class Storage {\r\n\tprivate storage: globalThis.Storage;\r\n\r\n\tconstructor(useSessionStorage = false) {\r\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\r\n\t}\r\n\r\n\tsetTokens(tokens: AuthTokens): void {\r\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\r\n\t}\r\n\r\n\tgetTokens(): AuthTokens | null {\r\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\r\n\t\treturn data ? JSON.parse(data) : null;\r\n\t}\r\n\r\n\tsetUser(user: User): void {\r\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\r\n\t}\r\n\r\n\tgetUser(): User | null {\r\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\r\n\t\treturn data ? JSON.parse(data) : null;\r\n\t}\r\n\r\n\tclear(): void {\r\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\r\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\r\n\t}\r\n\r\n\tsetState(key: string, value: string): void {\r\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\r\n\t}\r\n\r\n\tgetState(key: string): string | null {\r\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\r\n\t}\r\n\r\n\tremoveState(key: string): void {\r\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\r\n\t}\r\n}","import { AuthTokens, User } from './types';\r\n\r\nexport class ApiClient {\r\n\tprivate baseUrl: string;\r\n\r\n\tconstructor(baseUrl: string) {\r\n\t\tthis.baseUrl = baseUrl;\r\n\t}\r\n\r\n\tasync exchangeCodeForTokens(\r\n\t\tcode: string,\r\n\t\tclientId: string,\r\n\t\tredirectUri: string,\r\n\t\tclientSecret?: string\r\n\t): Promise<AuthTokens> {\r\n\t\tconst response = await fetch(`${this.baseUrl}/oauth/token`, {\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\t'Content-Type': 'application/json',\r\n\t\t\t},\r\n\t\t\tbody: JSON.stringify({\r\n\t\t\t\tgrant_type: 'authorization_code',\r\n\t\t\t\tcode,\r\n\t\t\t\tclient_id: clientId,\r\n\t\t\t\tclient_secret: clientSecret,\r\n\t\t\t\tredirect_uri: redirectUri,\r\n\t\t\t}),\r\n\t\t});\r\n\r\n\t\tif (!response.ok) {\r\n\t\t\tthrow new Error('Failed to exchange code for tokens');\r\n\t\t}\r\n\r\n\t\tconst data = await response.json();\r\n\t\treturn {\r\n\t\t\taccessToken: data.access_token,\r\n\t\t\trefreshToken: data.refresh_token,\r\n\t\t\texpiresIn: data.expires_in,\r\n\t\t\ttokenType: data.token_type,\r\n\t\t};\r\n\t}\r\n\r\n\tasync getUserInfo(accessToken: string): Promise<User> {\r\n\t\tconst response = await fetch(`${this.baseUrl}/oauth/userinfo`, {\r\n\t\t\theaders: {\r\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\r\n\t\t\t},\r\n\t\t});\r\n\r\n\t\tif (!response.ok) {\r\n\t\t\tthrow new Error('Failed to get user info');\r\n\t\t}\r\n\r\n\t\treturn response.json();\r\n\t}\r\n\r\n\tasync refreshAccessToken(\r\n\t\trefreshToken: string,\r\n\t\tclientId: string,\r\n\t\tclientSecret?: string\r\n\t): Promise<AuthTokens> {\r\n\t\tconst response = await fetch(`${this.baseUrl}/oauth/token`, {\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\t'Content-Type': 'application/json',\r\n\t\t\t},\r\n\t\t\tbody: JSON.stringify({\r\n\t\t\t\tgrant_type: 'refresh_token',\r\n\t\t\t\trefresh_token: refreshToken,\r\n\t\t\t\tclient_id: clientId,\r\n\t\t\t\tclient_secret: clientSecret,\r\n\t\t\t}),\r\n\t\t});\r\n\r\n\t\tif (!response.ok) {\r\n\t\t\tthrow new Error('Failed to refresh token');\r\n\t\t}\r\n\r\n\t\tconst data = await response.json();\r\n\t\treturn {\r\n\t\t\taccessToken: data.access_token,\r\n\t\t\trefreshToken: data.refresh_token,\r\n\t\t\texpiresIn: data.expires_in,\r\n\t\t\ttokenType: data.token_type,\r\n\t\t};\r\n\t}\r\n\r\n\tasync logout(accessToken: string): Promise<void> {\r\n\t\tawait fetch(`${this.baseUrl}/oauth/revoke`, {\r\n\t\t\tmethod: 'POST',\r\n\t\t\theaders: {\r\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\r\n\t\t\t},\r\n\t\t});\r\n\t}\r\n}","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\r\nimport { Storage } from './storage';\r\nimport { ApiClient } from './api';\r\nimport { generateState, parseQueryString, isTokenExpired } from './utils';\r\n\r\nexport class Custos {\r\n\tprivate config: Required<CustosConfig>;\r\n\tprivate storage: Storage;\r\n\tprivate api: ApiClient;\r\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\r\n\tprivate tokenIssuedAt: number | null = null;\r\n\r\n\tconstructor(config: CustosConfig) {\r\n\t\tthis.config = {\r\n\t\t\tclientId: config.clientId,\r\n\t\t\tclientSecret: config.clientSecret || '',\r\n\t\t\tredirectUri: config.redirectUri,\r\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\r\n\t\t\tscope: config.scope || ['openid', 'profile', 'email'],\r\n\t\t};\r\n\r\n\t\tthis.storage = new Storage();\r\n\t\tthis.api = new ApiClient(this.config.apiUrl);\r\n\t\tthis.listeners = new Map();\r\n\r\n\t\t// Handle callback automatically\r\n\t\tthis.handleCallback();\r\n\r\n\t\t// Setup token refresh\r\n\t\tthis.setupTokenRefresh();\r\n\t}\r\n\r\n\t// Authentication Methods\r\n\tasync login(): Promise<void> {\r\n\t\tconst state = generateState();\r\n\t\tthis.storage.setState('oauth_state', state);\r\n\r\n\t\tconst params = new URLSearchParams({\r\n\t\t\tresponse_type: 'code',\r\n\t\t\tclient_id: this.config.clientId,\r\n\t\t\tredirect_uri: this.config.redirectUri,\r\n\t\t\tscope: this.config.scope.join(' '),\r\n\t\t\tstate,\r\n\t\t});\r\n\r\n\t\twindow.location.href = `${this.config.apiUrl}/oauth/authorize?${params}`;\r\n\t}\r\n\r\n\tasync logout(): Promise<void> {\r\n\t\tconst tokens = this.storage.getTokens();\r\n\r\n\t\tif (tokens?.accessToken) {\r\n\t\t\ttry {\r\n\t\t\t\tawait this.api.logout(tokens.accessToken);\r\n\t\t\t} catch (error) {\r\n\t\t\t\tconsole.error('Logout error:', error);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.storage.clear();\r\n\t\tthis.emit('logout', null);\r\n\t}\r\n\r\n\tasync handleCallback(): Promise<void> {\r\n\t\tconst params = parseQueryString(window.location.href);\r\n\t\tconst code = params.code;\r\n\t\tconst state = params.state;\r\n\r\n\t\tif (!code) return;\r\n\r\n\t\tconst savedState = this.storage.getState('oauth_state');\r\n\t\tif (state !== savedState) {\r\n\t\t\tthrow new Error('Invalid state parameter');\r\n\t\t}\r\n\r\n\t\tthis.storage.removeState('oauth_state');\r\n\r\n\t\ttry {\r\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\r\n\t\t\t\tcode,\r\n\t\t\t\tthis.config.clientId,\r\n\t\t\t\tthis.config.redirectUri,\r\n\t\t\t\tthis.config.clientSecret\r\n\t\t\t);\r\n\r\n\t\t\tthis.tokenIssuedAt = Date.now();\r\n\t\t\tthis.storage.setTokens(tokens);\r\n\r\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\r\n\t\t\tthis.storage.setUser(user);\r\n\r\n\t\t\tthis.emit('login', { user, tokens });\r\n\r\n\t\t\t// Clean URL\r\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname);\r\n\t\t} catch (error) {\r\n\t\t\tthis.emit('error', error);\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t// User Methods\r\n\tgetUser(): User | null {\r\n\t\treturn this.storage.getUser();\r\n\t}\r\n\r\n\tgetAccessToken(): string | null {\r\n\t\treturn this.storage.getTokens()?.accessToken || null;\r\n\t}\r\n\r\n\tisAuthenticated(): boolean {\r\n\t\treturn !!this.storage.getTokens() && !!this.storage.getUser();\r\n\t}\r\n\r\n\tgetState(): AuthState {\r\n\t\treturn {\r\n\t\t\tisAuthenticated: this.isAuthenticated(),\r\n\t\t\tuser: this.getUser(),\r\n\t\t\ttokens: this.storage.getTokens(),\r\n\t\t};\r\n\t}\r\n\r\n\t// Token Refresh\r\n\tprivate setupTokenRefresh(): void {\r\n\t\tsetInterval(async () => {\r\n\t\t\tif (this.shouldRefreshToken()) {\r\n\t\t\t\tawait this.refreshToken();\r\n\t\t\t}\r\n\t\t}, 60000); // Check every minute\r\n\t}\r\n\r\n\tprivate shouldRefreshToken(): boolean {\r\n\t\tconst tokens = this.storage.getTokens();\r\n\t\tif (!tokens || !this.tokenIssuedAt) return false;\r\n\r\n\t\treturn isTokenExpired(tokens.expiresIn, this.tokenIssuedAt);\r\n\t}\r\n\r\n\tasync refreshToken(): Promise<void> {\r\n\t\tconst tokens = this.storage.getTokens();\r\n\t\tif (!tokens?.refreshToken) {\r\n\t\t\tthrow new Error('No refresh token available');\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\r\n\t\t\t\ttokens.refreshToken,\r\n\t\t\t\tthis.config.clientId,\r\n\t\t\t\tthis.config.clientSecret\r\n\t\t\t);\r\n\r\n\t\t\tthis.tokenIssuedAt = Date.now();\r\n\t\t\tthis.storage.setTokens(newTokens);\r\n\r\n\t\t\tthis.emit('token-refresh', newTokens);\r\n\t\t} catch (error) {\r\n\t\t\tthis.emit('error', error);\r\n\t\t\t// If refresh fails, logout\r\n\t\t\tawait this.logout();\r\n\t\t\tthrow error;\r\n\t\t}\r\n\t}\r\n\r\n\t// Event Handling\r\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\r\n\t\tif (!this.listeners.has(event)) {\r\n\t\t\tthis.listeners.set(event, new Set());\r\n\t\t}\r\n\t\tthis.listeners.get(event)!.add(callback);\r\n\t}\r\n\r\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\r\n\t\tthis.listeners.get(event)?.delete(callback);\r\n\t}\r\n\r\n\tprivate emit(type: AuthEventType, data?: any): void {\r\n\t\tconst event: AuthEvent = { type, data };\r\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\r\n\t}\r\n}","export function generateState(): string {\r\n\tconst array = new Uint8Array(32);\r\n\tcrypto.getRandomValues(array);\r\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\r\n}\r\n\r\nexport function parseQueryString(url: string): Record<string, string> {\r\n\tconst params: Record<string, string> = {};\r\n\tconst searchParams = new URL(url).searchParams;\r\n\tsearchParams.forEach((value, key) => {\r\n\t\tparams[key] = value;\r\n\t});\r\n\treturn params;\r\n}\r\n\r\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\r\n\tconst now = Date.now();\r\n\tconst expirationTime = issuedAt + expiresIn * 1000;\r\n\t// Refresh 5 minutes before expiration\r\n\treturn now >= expirationTime - 5 * 60 * 1000;\r\n}"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","getTokens","data","getItem","parse","setUser","user","getUser","clear","removeItem","setState","key","value","getState","removeState","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","response","fetch","method","headers","body","grant_type","client_id","client_secret","redirect_uri","ok","Error","json","accessToken","access_token","refreshToken","refresh_token","expiresIn","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","config","tokenIssuedAt","apiUrl","scope","api","listeners","Map","handleCallback","setupTokenRefresh","login","state","array","Uint8Array","crypto","getRandomValues","Array","from","byte","toString","padStart","join","generateState","params","URLSearchParams","response_type","window","location","href","error","console","emit","url","URL","searchParams","forEach","parseQueryString","Date","now","history","replaceState","document","title","pathname","getAccessToken","isAuthenticated","setInterval","async","shouldRefreshToken","issuedAt","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type"],"mappings":"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"}
1
+ {"version":3,"file":"index.umd.js","sources":["../src/storage.ts","../src/api.ts","../src/utils.ts","../src/Custos.ts"],"sourcesContent":["import { AuthTokens, User } from './types';\n\nconst STORAGE_PREFIX = 'custos_';\n\nexport class Storage {\n\tprivate storage: globalThis.Storage;\n\n\tconstructor(useSessionStorage = false) {\n\t\tthis.storage = useSessionStorage ? sessionStorage : localStorage;\n\t}\n\n\t// Tokens\n\tsetTokens(tokens: AuthTokens): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());\n\t}\n\n\tgetTokens(): AuthTokens | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\tgetTokenIssuedAt(): number | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\treturn data ? parseInt(data, 10) : null;\n\t}\n\n\t// User\n\tsetUser(user: User): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));\n\t}\n\n\tgetUser(): User | null {\n\t\tconst data = this.storage.getItem(`${STORAGE_PREFIX}user`);\n\t\treturn data ? JSON.parse(data) : null;\n\t}\n\n\t// State & PKCE\n\tsetState(key: string, value: string): void {\n\t\tthis.storage.setItem(`${STORAGE_PREFIX}${key}`, value);\n\t}\n\n\tgetState(key: string): string | null {\n\t\treturn this.storage.getItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\tremoveState(key: string): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}${key}`);\n\t}\n\n\t// PKCE specific\n\tsetCodeVerifier(codeVerifier: string): void {\n\t\tthis.setState('code_verifier', codeVerifier);\n\t}\n\n\tgetCodeVerifier(): string | null {\n\t\treturn this.getState('code_verifier');\n\t}\n\n\tremoveCodeVerifier(): void {\n\t\tthis.removeState('code_verifier');\n\t}\n\n\tsetCodeChallenge(codeChallenge: string): void {\n\t\tthis.setState('code_challenge', codeChallenge);\n\t}\n\n\tgetCodeChallenge(): string | null {\n\t\treturn this.getState('code_challenge');\n\t}\n\n\tremoveCodeChallenge(): void {\n\t\tthis.removeState('code_challenge');\n\t}\n\n\t// Clear all\n\tclear(): void {\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}tokens`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);\n\t\tthis.storage.removeItem(`${STORAGE_PREFIX}user`);\n\t\tthis.removeState('oauth_state');\n\t\tthis.removeCodeVerifier();\n\t\tthis.removeCodeChallenge();\n\t}\n\n\t// Validation\n\thasValidToken(): boolean {\n\t\tconst tokens = this.getTokens();\n\t\tconst issuedAt = this.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return false;\n\n\t\tconst now = Date.now();\n\t\tconst expirationTime = issuedAt + tokens.expiresIn * 1000;\n\n\t\treturn now < expirationTime;\n\t}\n}\n","import { AuthTokens, User } from './types';\n\nexport class ApiClient {\n\tprivate baseUrl: string;\n\n\tconstructor(baseUrl: string) {\n\t\tthis.baseUrl = baseUrl;\n\t}\n\n\tasync exchangeCodeForTokens(\n\t\tcode: string,\n\t\tclientId: string,\n\t\tredirectUri: string,\n\t\tcodeVerifier?: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'authorization_code',\n\t\t\tcode,\n\t\t\tclient_id: clientId,\n\t\t\tredirect_uri: redirectUri,\n\t\t};\n\n\t\t// Add PKCE code_verifier if present\n\t\tif (codeVerifier) {\n\t\t\tbody.code_verifier = codeVerifier;\n\t\t}\n\n\t\t// Add client_secret if present (for confidential clients)\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tconst errorData = await response.json().catch(() => ({\n\t\t\t\terror: 'unknown_error',\n\t\t\t\terror_description: 'Failed to exchange code for tokens'\n\t\t\t}));\n\n\t\t\tthrow new Error(errorData.error_description || errorData.error || 'Token exchange failed');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result; // Support both {data: {...}} and direct response\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token,\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync getUserInfo(accessToken: string): Promise<User> {\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/system/users/profile`, {\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to get user info');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\treturn result.data || result;\n\t}\n\n\tasync refreshAccessToken(\n\t\trefreshToken: string,\n\t\tclientId: string,\n\t\tclientSecret?: string\n\t): Promise<AuthTokens> {\n\t\tconst body: Record<string, string> = {\n\t\t\tgrant_type: 'refresh_token',\n\t\t\trefresh_token: refreshToken,\n\t\t\tclient_id: clientId,\n\t\t};\n\n\t\tif (clientSecret) {\n\t\t\tbody.client_secret = clientSecret;\n\t\t}\n\n\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/token`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/x-www-form-urlencoded',\n\t\t\t},\n\t\t\tbody: new URLSearchParams(body).toString(),\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tthrow new Error('Failed to refresh token');\n\t\t}\n\n\t\tconst result = await response.json();\n\t\tconst data = result.data || result;\n\n\t\treturn {\n\t\t\taccessToken: data.access_token,\n\t\t\trefreshToken: data.refresh_token || refreshToken, // Keep old refresh token if not provided\n\t\t\texpiresIn: data.expires_in,\n\t\t\ttokenType: data.token_type || 'Bearer',\n\t\t};\n\t}\n\n\tasync logout(accessToken: string): Promise<void> {\n\t\tawait fetch(`${this.baseUrl}/api/v1/auth/revoke`, {\n\t\t\tmethod: 'POST',\n\t\t\theaders: {\n\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t},\n\t\t});\n\t}\n\n\tasync validateToken(accessToken: string): Promise<boolean> {\n\t\ttry {\n\t\t\tconst response = await fetch(`${this.baseUrl}/api/v1/auth/validate`, {\n\t\t\t\theaders: {\n\t\t\t\t\tAuthorization: `Bearer ${accessToken}`,\n\t\t\t\t},\n\t\t\t});\n\t\t\treturn response.ok;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n}\n","export function generateState(): string {\n\tconst array = new Uint8Array(32);\n\tcrypto.getRandomValues(array);\n\treturn Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');\n}\n\nexport function parseQueryString(url: string): Record<string, string> {\n\tconst params: Record<string, string> = {};\n\tconst searchParams = new URL(url).searchParams;\n\tsearchParams.forEach((value, key) => {\n\t\tparams[key] = value;\n\t});\n\treturn params;\n}\n\nexport function isTokenExpired(expiresIn: number, issuedAt: number): boolean {\n\tconst now = Date.now();\n\tconst expirationTime = issuedAt + expiresIn * 1000;\n\t// Refresh 5 minutes before expiration\n\treturn now >= expirationTime - 5 * 60 * 1000;\n}\n\n// PKCE utilities\nexport function generateCodeVerifier(): string {\n\tconst charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n\tconst length = Math.floor(Math.random() * 86) + 43; // 43-128 characters\n\tlet verifier = '';\n\tfor (let i = 0; i < length; i++) {\n\t\tverifier += charset.charAt(Math.floor(Math.random() * charset.length));\n\t}\n\treturn verifier;\n}\n\nexport async function generateCodeChallenge(codeVerifier: string): Promise<string> {\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(codeVerifier);\n\tconst digest = await crypto.subtle.digest('SHA-256', data);\n\treturn base64UrlEncode(digest);\n}\n\nfunction base64UrlEncode(digest: ArrayBuffer): string {\n\tconst bytes = new Uint8Array(digest);\n\tconst binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');\n\treturn btoa(binary)\n\t\t.replace(/\\+/g, '-')\n\t\t.replace(/\\//g, '_')\n\t\t.replace(/=/g, '');\n}\n\nexport function normalizeScope(scope?: string | string[]): string[] {\n\tif (!scope) return ['openid', 'profile', 'email'];\n\tif (Array.isArray(scope)) return scope;\n\tif (typeof scope === 'string') return scope.split(' ');\n\treturn ['openid', 'profile', 'email'];\n}\n","import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';\nimport { Storage } from './storage';\nimport { ApiClient } from './api';\nimport {\n\tgenerateState,\n\tparseQueryString,\n\tgenerateCodeVerifier,\n\tgenerateCodeChallenge,\n\tnormalizeScope\n} from './utils';\n\nexport class Custos {\n\tprivate config: Required<CustosConfig>;\n\tprivate storage: Storage;\n\tprivate api: ApiClient;\n\tprivate listeners: Map<AuthEventType, Set<(event: AuthEvent) => void>>;\n\tprivate tokenExpiryTimer: any = null;\n\n\tconstructor(config: CustosConfig) {\n\t\t// Normalize scope\n\t\tconst scope = normalizeScope(config.scope);\n\n\t\tthis.config = {\n\t\t\tclientId: config.clientId,\n\t\t\tclientSecret: config.clientSecret || '',\n\t\t\tredirectUri: config.redirectUri,\n\t\t\tapiUrl: config.apiUrl || 'https://custos.alimzen.com',\n\t\t\tscope,\n\t\t\tresponseType: config.responseType || 'code',\n\t\t\tstate: config.state || generateState(),\n\t\t\tusePKCE: config.usePKCE !== false, // Default to true\n\t\t\tcodeChallengeMethod: config.codeChallengeMethod || 'S256',\n\t\t\tgrantType: config.grantType || 'authorization_code',\n\t\t};\n\n\t\tthis.storage = new Storage();\n\t\tthis.api = new ApiClient(this.config.apiUrl);\n\t\tthis.listeners = new Map();\n\n\t\t// Handle callback automatically\n\t\tif (typeof window !== 'undefined') {\n\t\t\tthis.handleCallback();\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t}\n\t}\n\n\t// ==================== Authentication Methods ====================\n\n\tasync login(additionalParams?: Record<string, string>): Promise<void> {\n\t\tconst state = this.config.state;\n\t\tthis.storage.setState('oauth_state', state);\n\n\t\tconst params: Record<string, string> = {\n\t\t\tresponse_type: this.config.responseType,\n\t\t\tclient_id: this.config.clientId,\n\t\t\tredirect_uri: this.config.redirectUri,\n\t\t\tscope: Array.isArray(this.config.scope) ? this.config.scope.join(' ') : this.config.scope,\n\t\t\tstate,\n\t\t\t...additionalParams,\n\t\t};\n\n\t\t// Add PKCE if enabled\n\t\tif (this.config.usePKCE) {\n\t\t\tconst codeVerifier = generateCodeVerifier();\n\t\t\tconst codeChallenge = await generateCodeChallenge(codeVerifier);\n\n\t\t\tthis.storage.setCodeVerifier(codeVerifier);\n\t\t\tthis.storage.setCodeChallenge(codeChallenge);\n\n\t\t\tparams.code_challenge = codeChallenge;\n\t\t\tparams.code_challenge_method = this.config.codeChallengeMethod;\n\t\t}\n\n\t\tconst authUrl = `${this.config.apiUrl}/v1/auth/authorize?${new URLSearchParams(params)}`;\n\t\twindow.location.href = authUrl;\n\t}\n\n\tasync logout(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\n\t\tif (tokens?.accessToken) {\n\t\t\ttry {\n\t\t\t\tawait this.api.logout(tokens.accessToken);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('Logout error:', error);\n\t\t\t}\n\t\t}\n\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.storage.clear();\n\t\tthis.emit('logout', null);\n\t}\n\n\tasync handleCallback(): Promise<void> {\n\t\tconst params = parseQueryString(window.location.href);\n\n\t\t// Check for errors\n\t\tconst error = params.error;\n\t\tif (error) {\n\t\t\tconst errorDescription = params.error_description || error;\n\t\t\tthis.emit('error', { error, error_description: errorDescription });\n\t\t\tthrow new Error(errorDescription);\n\t\t}\n\n\t\t// Check for authorization code\n\t\tconst code = params.code;\n\t\tif (!code) return;\n\n\t\t// Validate state\n\t\tconst state = params.state;\n\t\tconst savedState = this.storage.getState('oauth_state');\n\t\tif (state !== savedState) {\n\t\t\tthis.emit('error', { error: 'invalid_state', error_description: 'State parameter mismatch' });\n\t\t\tthrow new Error('Invalid state parameter');\n\t\t}\n\n\t\tthis.storage.removeState('oauth_state');\n\n\t\ttry {\n\t\t\t// Get code_verifier if using PKCE\n\t\t\tconst codeVerifier = this.config.usePKCE ? this.storage.getCodeVerifier() || undefined : undefined;\n\n\t\t\t// Exchange code for tokens\n\t\t\tconst tokens = await this.api.exchangeCodeForTokens(\n\t\t\t\tcode,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.redirectUri,\n\t\t\t\tcodeVerifier,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(tokens);\n\n\t\t\t// Get user info\n\t\t\tconst user = await this.api.getUserInfo(tokens.accessToken);\n\t\t\tthis.storage.setUser(user);\n\n\t\t\t// Clean up PKCE data\n\t\t\tif (this.config.usePKCE) {\n\t\t\t\tthis.storage.removeCodeVerifier();\n\t\t\t\tthis.storage.removeCodeChallenge();\n\t\t\t}\n\n\t\t\t// Setup token expiry monitoring\n\t\t\tthis.setupTokenExpiryMonitoring();\n\n\t\t\tthis.emit('login', { user, tokens });\n\n\t\t\t// Clean URL (remove query params)\n\t\t\twindow.history.replaceState({}, document.title, window.location.pathname + window.location.hash);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== User Methods ====================\n\n\tgetUser(): User | null {\n\t\treturn this.storage.getUser();\n\t}\n\n\tgetAccessToken(): string | null {\n\t\treturn this.storage.getTokens()?.accessToken || null;\n\t}\n\n\tgetRefreshToken(): string | null {\n\t\treturn this.storage.getTokens()?.refreshToken || null;\n\t}\n\n\tisAuthenticated(): boolean {\n\t\treturn this.storage.hasValidToken() && !!this.storage.getUser();\n\t}\n\n\tgetState(): AuthState {\n\t\treturn {\n\t\t\tisAuthenticated: this.isAuthenticated(),\n\t\t\tuser: this.getUser(),\n\t\t\ttokens: this.storage.getTokens(),\n\t\t};\n\t}\n\n\tasync validateToken(): Promise<boolean> {\n\t\tconst accessToken = this.getAccessToken();\n\t\tif (!accessToken) return false;\n\n\t\ttry {\n\t\t\treturn await this.api.validateToken(accessToken);\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\t// ==================== Token Refresh ====================\n\n\tprivate setupTokenExpiryMonitoring(): void {\n\t\tthis.clearTokenExpiryTimer();\n\n\t\tconst tokens = this.storage.getTokens();\n\t\tconst issuedAt = this.storage.getTokenIssuedAt();\n\n\t\tif (!tokens || !issuedAt) return;\n\n\t\t// Refresh 5 minutes before expiry\n\t\tconst timeUntilRefresh = (tokens.expiresIn - 300) * 1000; // 5 min buffer\n\n\t\tif (timeUntilRefresh > 0) {\n\t\t\tthis.tokenExpiryTimer = setTimeout(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.refreshToken();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tthis.emit('token-expired', error);\n\t\t\t\t\tawait this.logout();\n\t\t\t\t}\n\t\t\t}, timeUntilRefresh);\n\t\t}\n\t}\n\n\tprivate clearTokenExpiryTimer(): void {\n\t\tif (this.tokenExpiryTimer) {\n\t\t\tclearTimeout(this.tokenExpiryTimer);\n\t\t\tthis.tokenExpiryTimer = null;\n\t\t}\n\t}\n\n\tasync refreshToken(): Promise<void> {\n\t\tconst tokens = this.storage.getTokens();\n\t\tif (!tokens?.refreshToken) {\n\t\t\tthrow new Error('No refresh token available');\n\t\t}\n\n\t\ttry {\n\t\t\tconst newTokens = await this.api.refreshAccessToken(\n\t\t\t\ttokens.refreshToken,\n\t\t\t\tthis.config.clientId,\n\t\t\t\tthis.config.clientSecret\n\t\t\t);\n\n\t\t\tthis.storage.setTokens(newTokens);\n\t\t\tthis.setupTokenExpiryMonitoring();\n\t\t\tthis.emit('token-refresh', newTokens);\n\t\t} catch (error) {\n\t\t\tthis.emit('error', error);\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\t// ==================== Event Handling ====================\n\n\ton(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)!.add(callback);\n\t}\n\n\toff(event: AuthEventType, callback: (event: AuthEvent) => void): void {\n\t\tthis.listeners.get(event)?.delete(callback);\n\t}\n\n\tprivate emit(type: AuthEventType, data?: any): void {\n\t\tconst event: AuthEvent = { type, data };\n\t\tthis.listeners.get(type)?.forEach((callback) => callback(event));\n\t}\n\n\t// ==================== Utility Methods ====================\n\n\tclearStorage(): void {\n\t\tthis.storage.clear();\n\t}\n\n\tdestroy(): void {\n\t\tthis.clearTokenExpiryTimer();\n\t\tthis.listeners.clear();\n\t}\n}\n"],"names":["STORAGE_PREFIX","Storage","constructor","useSessionStorage","this","storage","sessionStorage","localStorage","setTokens","tokens","setItem","JSON","stringify","Date","now","toString","getTokens","data","getItem","parse","getTokenIssuedAt","parseInt","setUser","user","getUser","setState","key","value","getState","removeState","removeItem","setCodeVerifier","codeVerifier","getCodeVerifier","removeCodeVerifier","setCodeChallenge","codeChallenge","getCodeChallenge","removeCodeChallenge","clear","hasValidToken","issuedAt","expiresIn","ApiClient","baseUrl","exchangeCodeForTokens","code","clientId","redirectUri","clientSecret","body","grant_type","client_id","redirect_uri","code_verifier","client_secret","response","fetch","method","headers","URLSearchParams","ok","errorData","json","catch","error","error_description","Error","result","accessToken","access_token","refreshToken","refresh_token","expires_in","tokenType","token_type","getUserInfo","Authorization","refreshAccessToken","logout","validateToken","generateState","array","Uint8Array","crypto","getRandomValues","Array","from","byte","padStart","join","async","generateCodeChallenge","TextEncoder","encode","digest","bytes","binary","String","fromCharCode","btoa","replace","base64UrlEncode","subtle","config","tokenExpiryTimer","scope","isArray","split","normalizeScope","apiUrl","responseType","state","usePKCE","codeChallengeMethod","grantType","api","listeners","Map","window","handleCallback","setupTokenExpiryMonitoring","login","additionalParams","params","response_type","charset","length","Math","floor","random","verifier","i","charAt","generateCodeVerifier","code_challenge","code_challenge_method","authUrl","location","href","console","clearTokenExpiryTimer","emit","url","URL","searchParams","forEach","parseQueryString","errorDescription","undefined","history","replaceState","document","title","pathname","hash","getAccessToken","getRefreshToken","isAuthenticated","timeUntilRefresh","setTimeout","clearTimeout","newTokens","on","event","callback","has","set","Set","get","add","off","delete","type","clearStorage","destroy"],"mappings":"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"}
package/dist/storage.d.ts CHANGED
@@ -4,11 +4,19 @@ export declare class Storage {
4
4
  constructor(useSessionStorage?: boolean);
5
5
  setTokens(tokens: AuthTokens): void;
6
6
  getTokens(): AuthTokens | null;
7
+ getTokenIssuedAt(): number | null;
7
8
  setUser(user: User): void;
8
9
  getUser(): User | null;
9
- clear(): void;
10
10
  setState(key: string, value: string): void;
11
11
  getState(key: string): string | null;
12
12
  removeState(key: string): void;
13
+ setCodeVerifier(codeVerifier: string): void;
14
+ getCodeVerifier(): string | null;
15
+ removeCodeVerifier(): void;
16
+ setCodeChallenge(codeChallenge: string): void;
17
+ getCodeChallenge(): string | null;
18
+ removeCodeChallenge(): void;
19
+ clear(): void;
20
+ hasValidToken(): boolean;
13
21
  }
14
22
  //# sourceMappingURL=storage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAI3C,qBAAa,OAAO;IACnB,OAAO,CAAC,OAAO,CAAqB;gBAExB,iBAAiB,UAAQ;IAIrC,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAInC,SAAS,IAAI,UAAU,GAAG,IAAI;IAK9B,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAIzB,OAAO,IAAI,IAAI,GAAG,IAAI;IAKtB,KAAK,IAAI,IAAI;IAKb,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAI1C,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIpC,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;CAG9B"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,SAAS,CAAC;AAI3C,qBAAa,OAAO;IACnB,OAAO,CAAC,OAAO,CAAqB;gBAExB,iBAAiB,UAAQ;IAKrC,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI;IAKnC,SAAS,IAAI,UAAU,GAAG,IAAI;IAK9B,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAMjC,OAAO,CAAC,IAAI,EAAE,IAAI,GAAG,IAAI;IAIzB,OAAO,IAAI,IAAI,GAAG,IAAI;IAMtB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAI1C,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAIpC,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI;IAK9B,eAAe,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI;IAI3C,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC,kBAAkB,IAAI,IAAI;IAI1B,gBAAgB,CAAC,aAAa,EAAE,MAAM,GAAG,IAAI;IAI7C,gBAAgB,IAAI,MAAM,GAAG,IAAI;IAIjC,mBAAmB,IAAI,IAAI;IAK3B,KAAK,IAAI,IAAI;IAUb,aAAa,IAAI,OAAO;CAWxB"}
@@ -0,0 +1,75 @@
1
+ const STORAGE_PREFIX = 'custos_';
2
+ export class Storage {
3
+ constructor(useSessionStorage = false) {
4
+ this.storage = useSessionStorage ? sessionStorage : localStorage;
5
+ }
6
+ // Tokens
7
+ setTokens(tokens) {
8
+ this.storage.setItem(`${STORAGE_PREFIX}tokens`, JSON.stringify(tokens));
9
+ this.storage.setItem(`${STORAGE_PREFIX}token_issued_at`, Date.now().toString());
10
+ }
11
+ getTokens() {
12
+ const data = this.storage.getItem(`${STORAGE_PREFIX}tokens`);
13
+ return data ? JSON.parse(data) : null;
14
+ }
15
+ getTokenIssuedAt() {
16
+ const data = this.storage.getItem(`${STORAGE_PREFIX}token_issued_at`);
17
+ return data ? parseInt(data, 10) : null;
18
+ }
19
+ // User
20
+ setUser(user) {
21
+ this.storage.setItem(`${STORAGE_PREFIX}user`, JSON.stringify(user));
22
+ }
23
+ getUser() {
24
+ const data = this.storage.getItem(`${STORAGE_PREFIX}user`);
25
+ return data ? JSON.parse(data) : null;
26
+ }
27
+ // State & PKCE
28
+ setState(key, value) {
29
+ this.storage.setItem(`${STORAGE_PREFIX}${key}`, value);
30
+ }
31
+ getState(key) {
32
+ return this.storage.getItem(`${STORAGE_PREFIX}${key}`);
33
+ }
34
+ removeState(key) {
35
+ this.storage.removeItem(`${STORAGE_PREFIX}${key}`);
36
+ }
37
+ // PKCE specific
38
+ setCodeVerifier(codeVerifier) {
39
+ this.setState('code_verifier', codeVerifier);
40
+ }
41
+ getCodeVerifier() {
42
+ return this.getState('code_verifier');
43
+ }
44
+ removeCodeVerifier() {
45
+ this.removeState('code_verifier');
46
+ }
47
+ setCodeChallenge(codeChallenge) {
48
+ this.setState('code_challenge', codeChallenge);
49
+ }
50
+ getCodeChallenge() {
51
+ return this.getState('code_challenge');
52
+ }
53
+ removeCodeChallenge() {
54
+ this.removeState('code_challenge');
55
+ }
56
+ // Clear all
57
+ clear() {
58
+ this.storage.removeItem(`${STORAGE_PREFIX}tokens`);
59
+ this.storage.removeItem(`${STORAGE_PREFIX}token_issued_at`);
60
+ this.storage.removeItem(`${STORAGE_PREFIX}user`);
61
+ this.removeState('oauth_state');
62
+ this.removeCodeVerifier();
63
+ this.removeCodeChallenge();
64
+ }
65
+ // Validation
66
+ hasValidToken() {
67
+ const tokens = this.getTokens();
68
+ const issuedAt = this.getTokenIssuedAt();
69
+ if (!tokens || !issuedAt)
70
+ return false;
71
+ const now = Date.now();
72
+ const expirationTime = issuedAt + tokens.expiresIn * 1000;
73
+ return now < expirationTime;
74
+ }
75
+ }
package/dist/types.d.ts CHANGED
@@ -3,7 +3,12 @@ export interface CustosConfig {
3
3
  clientSecret?: string;
4
4
  redirectUri: string;
5
5
  apiUrl?: string;
6
- scope?: string[];
6
+ scope?: string | string[];
7
+ responseType?: 'code' | 'token';
8
+ state?: string;
9
+ usePKCE?: boolean;
10
+ codeChallengeMethod?: 'S256' | 'plain';
11
+ grantType?: string;
7
12
  }
8
13
  export interface User {
9
14
  id: string;
@@ -24,9 +29,13 @@ export interface AuthState {
24
29
  user: User | null;
25
30
  tokens: AuthTokens | null;
26
31
  }
27
- export type AuthEventType = 'login' | 'logout' | 'token-refresh' | 'error';
32
+ export type AuthEventType = 'login' | 'logout' | 'token-refresh' | 'token-expired' | 'error';
28
33
  export interface AuthEvent {
29
34
  type: AuthEventType;
30
35
  data?: any;
31
36
  }
37
+ export interface PKCETokens {
38
+ code_verifier: string;
39
+ code_challenge: string;
40
+ }
32
41
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,IAAI;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,eAAe,GAAG,OAAO,CAAC;AAE3E,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,CAAC,EAAE,GAAG,CAAC;CACX"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC1B,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,mBAAmB,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;IACvC,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,IAAI;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,UAAU;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;IAClB,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;CAC1B;AAED,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,QAAQ,GAAG,eAAe,GAAG,eAAe,GAAG,OAAO,CAAC;AAE7F,MAAM,WAAW,SAAS;IACzB,IAAI,EAAE,aAAa,CAAC;IACpB,IAAI,CAAC,EAAE,GAAG,CAAC;CACX;AAED,MAAM,WAAW,UAAU;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CACvB"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/utils.d.ts CHANGED
@@ -1,4 +1,7 @@
1
1
  export declare function generateState(): string;
2
2
  export declare function parseQueryString(url: string): Record<string, string>;
3
3
  export declare function isTokenExpired(expiresIn: number, issuedAt: number): boolean;
4
+ export declare function generateCodeVerifier(): string;
5
+ export declare function generateCodeChallenge(codeVerifier: string): Promise<string>;
6
+ export declare function normalizeScope(scope?: string | string[]): string[];
4
7
  //# sourceMappingURL=utils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOpE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK3E"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,aAAa,IAAI,MAAM,CAItC;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAOpE;AAED,wBAAgB,cAAc,CAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAK3E;AAGD,wBAAgB,oBAAoB,IAAI,MAAM,CAQ7C;AAED,wBAAsB,qBAAqB,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKjF;AAWD,wBAAgB,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,MAAM,EAAE,CAKlE"}
package/dist/utils.js ADDED
@@ -0,0 +1,52 @@
1
+ export function generateState() {
2
+ const array = new Uint8Array(32);
3
+ crypto.getRandomValues(array);
4
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, '0')).join('');
5
+ }
6
+ export function parseQueryString(url) {
7
+ const params = {};
8
+ const searchParams = new URL(url).searchParams;
9
+ searchParams.forEach((value, key) => {
10
+ params[key] = value;
11
+ });
12
+ return params;
13
+ }
14
+ export function isTokenExpired(expiresIn, issuedAt) {
15
+ const now = Date.now();
16
+ const expirationTime = issuedAt + expiresIn * 1000;
17
+ // Refresh 5 minutes before expiration
18
+ return now >= expirationTime - 5 * 60 * 1000;
19
+ }
20
+ // PKCE utilities
21
+ export function generateCodeVerifier() {
22
+ const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';
23
+ const length = Math.floor(Math.random() * 86) + 43; // 43-128 characters
24
+ let verifier = '';
25
+ for (let i = 0; i < length; i++) {
26
+ verifier += charset.charAt(Math.floor(Math.random() * charset.length));
27
+ }
28
+ return verifier;
29
+ }
30
+ export async function generateCodeChallenge(codeVerifier) {
31
+ const encoder = new TextEncoder();
32
+ const data = encoder.encode(codeVerifier);
33
+ const digest = await crypto.subtle.digest('SHA-256', data);
34
+ return base64UrlEncode(digest);
35
+ }
36
+ function base64UrlEncode(digest) {
37
+ const bytes = new Uint8Array(digest);
38
+ const binary = Array.from(bytes, (byte) => String.fromCharCode(byte)).join('');
39
+ return btoa(binary)
40
+ .replace(/\+/g, '-')
41
+ .replace(/\//g, '_')
42
+ .replace(/=/g, '');
43
+ }
44
+ export function normalizeScope(scope) {
45
+ if (!scope)
46
+ return ['openid', 'profile', 'email'];
47
+ if (Array.isArray(scope))
48
+ return scope;
49
+ if (typeof scope === 'string')
50
+ return scope.split(' ');
51
+ return ['openid', 'profile', 'email'];
52
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@isaias_pv/custos-sdk",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Official JavaScript SDK for Custos authentication",
5
5
  "main": "dist/index.cjs.js",
6
6
  "module": "dist/index.esm.js",
@@ -60,4 +60,4 @@
60
60
  "dependencies": {
61
61
  "tslib": "^2.8.1"
62
62
  }
63
- }
63
+ }