@isaias_pv/custos-sdk 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +157 -0
- package/dist/Custos.d.ts +23 -0
- package/dist/Custos.d.ts.map +1 -0
- package/dist/api.d.ts +10 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/index.cjs.js +2 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.esm.js +2 -0
- package/dist/index.esm.js.map +1 -0
- package/dist/index.umd.js +2 -0
- package/dist/index.umd.js.map +1 -0
- package/dist/storage.d.ts +14 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/types.d.ts +32 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils.d.ts +4 -0
- package/dist/utils.d.ts.map +1 -0
- package/package.json +63 -0
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# @custos/sdk
|
|
2
|
+
|
|
3
|
+
Official JavaScript SDK for Custos Authentication
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```bash
|
|
7
|
+
npm install @custos/sdk
|
|
8
|
+
# or
|
|
9
|
+
yarn add @custos/sdk
|
|
10
|
+
# or
|
|
11
|
+
pnpm add @custos/sdk
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
```javascript
|
|
16
|
+
import { Custos } from '@custos/sdk'
|
|
17
|
+
|
|
18
|
+
const auth = new Custos({
|
|
19
|
+
clientId: 'your-client-id',
|
|
20
|
+
redirectUri: 'https://your-app.com/callback'
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
// Login
|
|
24
|
+
await auth.login()
|
|
25
|
+
|
|
26
|
+
// Get user
|
|
27
|
+
const user = auth.getUser()
|
|
28
|
+
console.log(user.email)
|
|
29
|
+
|
|
30
|
+
// Logout
|
|
31
|
+
await auth.logout()
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Configuration
|
|
35
|
+
```typescript
|
|
36
|
+
interface CustosConfig {
|
|
37
|
+
clientId: string; // Required
|
|
38
|
+
clientSecret?: string; // Optional (for server-side)
|
|
39
|
+
redirectUri: string; // Required
|
|
40
|
+
apiUrl?: string; // Optional (default: https://custos.alimzen.com)
|
|
41
|
+
scope?: string[]; // Optional (default: ['openid', 'profile', 'email'])
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## API Reference
|
|
46
|
+
|
|
47
|
+
### Methods
|
|
48
|
+
|
|
49
|
+
#### `login()`
|
|
50
|
+
Redirects user to Custos login page
|
|
51
|
+
|
|
52
|
+
#### `logout()`
|
|
53
|
+
Logs out user and clears tokens
|
|
54
|
+
|
|
55
|
+
#### `getUser()`
|
|
56
|
+
Returns current user or null
|
|
57
|
+
|
|
58
|
+
#### `getAccessToken()`
|
|
59
|
+
Returns access token or null
|
|
60
|
+
|
|
61
|
+
#### `isAuthenticated()`
|
|
62
|
+
Returns boolean
|
|
63
|
+
|
|
64
|
+
#### `getState()`
|
|
65
|
+
Returns complete auth state
|
|
66
|
+
|
|
67
|
+
### Events
|
|
68
|
+
```javascript
|
|
69
|
+
auth.on('login', (event) => {
|
|
70
|
+
console.log('User logged in:', event.data.user)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
auth.on('logout', () => {
|
|
74
|
+
console.log('User logged out')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
auth.on('token-refresh', (event) => {
|
|
78
|
+
console.log('Token refreshed:', event.data)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
auth.on('error', (event) => {
|
|
82
|
+
console.error('Auth error:', event.data)
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Framework Examples
|
|
87
|
+
|
|
88
|
+
### React
|
|
89
|
+
```jsx
|
|
90
|
+
import { useState, useEffect } from 'react'
|
|
91
|
+
import { Custos } from '@custos/sdk'
|
|
92
|
+
|
|
93
|
+
const auth = new Custos({
|
|
94
|
+
clientId: 'your-client-id',
|
|
95
|
+
redirectUri: 'http://localhost:3000/callback'
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
function App() {
|
|
99
|
+
const [user, setUser] = useState(null)
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
setUser(auth.getUser())
|
|
103
|
+
|
|
104
|
+
auth.on('login', (e) => setUser(e.data.user))
|
|
105
|
+
auth.on('logout', () => setUser(null))
|
|
106
|
+
}, [])
|
|
107
|
+
|
|
108
|
+
if (!user) {
|
|
109
|
+
return <button onClick={() => auth.login()}>Login</button>
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return (
|
|
113
|
+
<div>
|
|
114
|
+
<h1>Welcome {user.name}</h1>
|
|
115
|
+
<button onClick={() => auth.logout()}>Logout</button>
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### Vue
|
|
122
|
+
```vue
|
|
123
|
+
<template>
|
|
124
|
+
<div>
|
|
125
|
+
<button v-if="!user" @click="login">Login</button>
|
|
126
|
+
<div v-else>
|
|
127
|
+
<h1>Welcome {{ user.name }}</h1>
|
|
128
|
+
<button @click="logout">Logout</button>
|
|
129
|
+
</div>
|
|
130
|
+
</div>
|
|
131
|
+
</template>
|
|
132
|
+
|
|
133
|
+
<script setup>
|
|
134
|
+
import { ref, onMounted } from 'vue'
|
|
135
|
+
import { Custos } from '@custos/sdk'
|
|
136
|
+
|
|
137
|
+
const auth = new Custos({
|
|
138
|
+
clientId: 'your-client-id',
|
|
139
|
+
redirectUri: 'http://localhost:3000/callback'
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const user = ref(null)
|
|
143
|
+
|
|
144
|
+
onMounted(() => {
|
|
145
|
+
user.value = auth.getUser()
|
|
146
|
+
auth.on('login', (e) => user.value = e.data.user)
|
|
147
|
+
auth.on('logout', () => user.value = null)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
const login = () => auth.login()
|
|
151
|
+
const logout = () => auth.logout()
|
|
152
|
+
</script>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT © Alim
|
package/dist/Custos.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { CustosConfig, User, AuthState, AuthEvent, AuthEventType } from './types';
|
|
2
|
+
export declare class Custos {
|
|
3
|
+
private config;
|
|
4
|
+
private storage;
|
|
5
|
+
private api;
|
|
6
|
+
private listeners;
|
|
7
|
+
private tokenIssuedAt;
|
|
8
|
+
constructor(config: CustosConfig);
|
|
9
|
+
login(): Promise<void>;
|
|
10
|
+
logout(): Promise<void>;
|
|
11
|
+
handleCallback(): Promise<void>;
|
|
12
|
+
getUser(): User | null;
|
|
13
|
+
getAccessToken(): string | null;
|
|
14
|
+
isAuthenticated(): boolean;
|
|
15
|
+
getState(): AuthState;
|
|
16
|
+
private setupTokenRefresh;
|
|
17
|
+
private shouldRefreshToken;
|
|
18
|
+
refreshToken(): Promise<void>;
|
|
19
|
+
on(event: AuthEventType, callback: (event: AuthEvent) => void): void;
|
|
20
|
+
off(event: AuthEventType, callback: (event: AuthEvent) => void): void;
|
|
21
|
+
private emit;
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=Custos.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/api.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { AuthTokens, User } from './types';
|
|
2
|
+
export declare class ApiClient {
|
|
3
|
+
private baseUrl;
|
|
4
|
+
constructor(baseUrl: string);
|
|
5
|
+
exchangeCodeForTokens(code: string, clientId: string, redirectUri: string, clientSecret?: string): Promise<AuthTokens>;
|
|
6
|
+
getUserInfo(accessToken: string): Promise<User>;
|
|
7
|
+
refreshAccessToken(refreshToken: string, clientId: string, clientSecret?: string): Promise<AuthTokens>;
|
|
8
|
+
logout(accessToken: string): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=api.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +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))}};
|
|
2
|
+
//# sourceMappingURL=index.cjs.js.map
|
|
@@ -0,0 +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":"oEAEA,MAAMA,EAAiB,gBAEVC,EAGZ,WAAAC,CAAYC,GAAoB,GAC/BC,KAAKC,QAAUF,EAAoBG,eAAiBC,YACpD,CAED,SAAAC,CAAUC,GACTL,KAAKC,QAAQK,QAAQ,GAAGV,UAAwBW,KAAKC,UAAUH,GAC/D,CAED,SAAAI,GACC,MAAMC,EAAOV,KAAKC,QAAQU,QAAQ,GAAGf,WACrC,OAAOc,EAAOH,KAAKK,MAAMF,GAAQ,IACjC,CAED,OAAAG,CAAQC,GACPd,KAAKC,QAAQK,QAAQ,GAAGV,QAAsBW,KAAKC,UAAUM,GAC7D,CAED,OAAAC,GACC,MAAML,EAAOV,KAAKC,QAAQU,QAAQ,GAAGf,SACrC,OAAOc,EAAOH,KAAKK,MAAMF,GAAQ,IACjC,CAED,KAAAM,GACChB,KAAKC,QAAQgB,WAAW,GAAGrB,WAC3BI,KAAKC,QAAQgB,WAAW,GAAGrB,QAC3B,CAED,QAAAsB,CAASC,EAAaC,GACrBpB,KAAKC,QAAQK,QAAQ,GAAGV,IAAiBuB,IAAOC,EAChD,CAED,QAAAC,CAASF,GACR,OAAOnB,KAAKC,QAAQU,QAAQ,GAAGf,IAAiBuB,IAChD,CAED,WAAAG,CAAYH,GACXnB,KAAKC,QAAQgB,WAAW,GAAGrB,IAAiBuB,IAC5C,QC1CWI,EAGZ,WAAAzB,CAAY0B,GACXxB,KAAKwB,QAAUA,CACf,CAED,2BAAMC,CACLC,EACAC,EACAC,EACAC,GAEA,MAAMC,QAAiBC,MAAM,GAAG/B,KAAKwB,sBAAuB,CAC3DQ,OAAQ,OACRC,QAAS,CACR,eAAgB,oBAEjBC,KAAM3B,KAAKC,UAAU,CACpB2B,WAAY,qBACZT,OACAU,UAAWT,EACXU,cAAeR,EACfS,aAAcV,MAIhB,IAAKE,EAASS,GACb,MAAM,IAAIC,MAAM,sCAGjB,MAAM9B,QAAaoB,EAASW,OAC5B,MAAO,CACNC,YAAahC,EAAKiC,aAClBC,aAAclC,EAAKmC,cACnBC,UAAWpC,EAAKqC,WAChBC,UAAWtC,EAAKuC,WAEjB,CAED,iBAAMC,CAAYR,GACjB,MAAMZ,QAAiBC,MAAM,GAAG/B,KAAKwB,yBAA0B,CAC9DS,QAAS,CACRkB,cAAe,UAAUT,OAI3B,IAAKZ,EAASS,GACb,MAAM,IAAIC,MAAM,2BAGjB,OAAOV,EAASW,MAChB,CAED,wBAAMW,CACLR,EACAjB,EACAE,GAEA,MAAMC,QAAiBC,MAAM,GAAG/B,KAAKwB,sBAAuB,CAC3DQ,OAAQ,OACRC,QAAS,CACR,eAAgB,oBAEjBC,KAAM3B,KAAKC,UAAU,CACpB2B,WAAY,gBACZU,cAAeD,EACfR,UAAWT,EACXU,cAAeR,MAIjB,IAAKC,EAASS,GACb,MAAM,IAAIC,MAAM,2BAGjB,MAAM9B,QAAaoB,EAASW,OAC5B,MAAO,CACNC,YAAahC,EAAKiC,aAClBC,aAAclC,EAAKmC,cACnBC,UAAWpC,EAAKqC,WAChBC,UAAWtC,EAAKuC,WAEjB,CAED,YAAMI,CAAOX,SACNX,MAAM,GAAG/B,KAAKwB,uBAAwB,CAC3CQ,OAAQ,OACRC,QAAS,CACRkB,cAAe,UAAUT,MAG3B,uBClFD,WAAA5C,CAAYwD,GAFJtD,KAAauD,cAAkB,KAGtCvD,KAAKsD,OAAS,CACb3B,SAAU2B,EAAO3B,SACjBE,aAAcyB,EAAOzB,cAAgB,GACrCD,YAAa0B,EAAO1B,YACpB4B,OAAQF,EAAOE,QAAU,6BACzBC,MAAOH,EAAOG,OAAS,CAAC,SAAU,UAAW,UAG9CzD,KAAKC,QAAU,IAAIJ,EACnBG,KAAK0D,IAAM,IAAInC,EAAUvB,KAAKsD,OAAOE,QACrCxD,KAAK2D,UAAY,IAAIC,IAGrB5D,KAAK6D,iBAGL7D,KAAK8D,mBACL,CAGD,WAAMC,GACL,MAAMC,aCjCP,MAAMC,EAAQ,IAAIC,WAAW,IAE7B,OADAC,OAAOC,gBAAgBH,GAChBI,MAAMC,KAAKL,EAAQM,GAASA,EAAKC,SAAS,IAAIC,SAAS,EAAG,MAAMC,KAAK,GAC7E,CD8BgBC,GACd3E,KAAKC,QAAQiB,SAAS,cAAe8C,GAErC,MAAMY,EAAS,IAAIC,gBAAgB,CAClCC,cAAe,OACf1C,UAAWpC,KAAKsD,OAAO3B,SACvBW,aAActC,KAAKsD,OAAO1B,YAC1B6B,MAAOzD,KAAKsD,OAAOG,MAAMiB,KAAK,KAC9BV,UAGDe,OAAOC,SAASC,KAAO,GAAGjF,KAAKsD,OAAOE,0BAA0BoB,GAChE,CAED,YAAMvB,GACL,MAAMhD,EAASL,KAAKC,QAAQQ,YAE5B,GAAIJ,GAAQqC,YACX,UACO1C,KAAK0D,IAAIL,OAAOhD,EAAOqC,YAC7B,CAAC,MAAOwC,GACRC,QAAQD,MAAM,gBAAiBA,EAC/B,CAGFlF,KAAKC,QAAQe,QACbhB,KAAKoF,KAAK,SAAU,KACpB,CAED,oBAAMvB,GACL,MAAMe,EC1DF,SAA2BS,GAChC,MAAMT,EAAiC,CAAA,EAKvC,OAJqB,IAAIU,IAAID,GAAKE,aACrBC,QAAQ,CAACpE,EAAOD,KAC5ByD,EAAOzD,GAAOC,IAERwD,CACR,CDmDiBa,CAAiBV,OAAOC,SAASC,MAC1CvD,EAAOkD,EAAOlD,KACdsC,EAAQY,EAAOZ,MAErB,IAAKtC,EAAM,OAGX,GAAIsC,IADehE,KAAKC,QAAQoB,SAAS,eAExC,MAAM,IAAImB,MAAM,2BAGjBxC,KAAKC,QAAQqB,YAAY,eAEzB,IACC,MAAMjB,QAAeL,KAAK0D,IAAIjC,sBAC7BC,EACA1B,KAAKsD,OAAO3B,SACZ3B,KAAKsD,OAAO1B,YACZ5B,KAAKsD,OAAOzB,cAGb7B,KAAKuD,cAAgBmC,KAAKC,MAC1B3F,KAAKC,QAAQG,UAAUC,GAEvB,MAAMS,QAAad,KAAK0D,IAAIR,YAAY7C,EAAOqC,aAC/C1C,KAAKC,QAAQY,QAAQC,GAErBd,KAAKoF,KAAK,QAAS,CAAEtE,OAAMT,WAG3B0E,OAAOa,QAAQC,aAAa,GAAIC,SAASC,MAAOhB,OAAOC,SAASgB,SAChE,CAAC,MAAOd,GAER,MADAlF,KAAKoF,KAAK,QAASF,GACbA,CACN,CACD,CAGD,OAAAnE,GACC,OAAOf,KAAKC,QAAQc,SACpB,CAED,cAAAkF,GACC,OAAOjG,KAAKC,QAAQQ,aAAaiC,aAAe,IAChD,CAED,eAAAwD,GACC,QAASlG,KAAKC,QAAQQ,eAAiBT,KAAKC,QAAQc,SACpD,CAED,QAAAM,GACC,MAAO,CACN6E,gBAAiBlG,KAAKkG,kBACtBpF,KAAMd,KAAKe,UACXV,OAAQL,KAAKC,QAAQQ,YAEtB,CAGO,iBAAAqD,GACPqC,YAAYC,UACPpG,KAAKqG,4BACFrG,KAAK4C,gBAEV,IACH,CAEO,kBAAAyD,GACP,MAAMhG,EAASL,KAAKC,QAAQQ,YAC5B,SAAKJ,IAAWL,KAAKuD,iBCtHQT,EDwHPzC,EAAOyC,UCxHmBwD,EDwHRtG,KAAKuD,cCvHlCmC,KAAKC,OACMW,EAAuB,IAAZxD,EAEH,KAJhB,IAAeA,EAAmBwD,CDyHhD,CAED,kBAAM1D,GACL,MAAMvC,EAASL,KAAKC,QAAQQ,YAC5B,IAAKJ,GAAQuC,aACZ,MAAM,IAAIJ,MAAM,8BAGjB,IACC,MAAM+D,QAAkBvG,KAAK0D,IAAIN,mBAChC/C,EAAOuC,aACP5C,KAAKsD,OAAO3B,SACZ3B,KAAKsD,OAAOzB,cAGb7B,KAAKuD,cAAgBmC,KAAKC,MAC1B3F,KAAKC,QAAQG,UAAUmG,GAEvBvG,KAAKoF,KAAK,gBAAiBmB,EAC3B,CAAC,MAAOrB,GAIR,MAHAlF,KAAKoF,KAAK,QAASF,SAEblF,KAAKqD,SACL6B,CACN,CACD,CAGD,EAAAsB,CAAGC,EAAsBC,GACnB1G,KAAK2D,UAAUgD,IAAIF,IACvBzG,KAAK2D,UAAUiD,IAAIH,EAAO,IAAII,KAE/B7G,KAAK2D,UAAUmD,IAAIL,GAAQM,IAAIL,EAC/B,CAED,GAAAM,CAAIP,EAAsBC,GACzB1G,KAAK2D,UAAUmD,IAAIL,IAAQQ,OAAOP,EAClC,CAEO,IAAAtB,CAAK8B,EAAqBxG,GACjC,MAAM+F,EAAmB,CAAES,OAAMxG,QACjCV,KAAK2D,UAAUmD,IAAII,IAAO1B,QAASkB,GAAaA,EAASD,GACzD"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAC;AAClC,YAAY,EACX,YAAY,EACZ,IAAI,EACJ,UAAU,EACV,SAAS,EACT,SAAS,EACT,aAAa,GACb,MAAM,SAAS,CAAC"}
|
|
@@ -0,0 +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};
|
|
2
|
+
//# sourceMappingURL=index.esm.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +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})});
|
|
2
|
+
//# sourceMappingURL=index.umd.js.map
|
|
@@ -0,0 +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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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { AuthTokens, User } from './types';
|
|
2
|
+
export declare class Storage {
|
|
3
|
+
private storage;
|
|
4
|
+
constructor(useSessionStorage?: boolean);
|
|
5
|
+
setTokens(tokens: AuthTokens): void;
|
|
6
|
+
getTokens(): AuthTokens | null;
|
|
7
|
+
setUser(user: User): void;
|
|
8
|
+
getUser(): User | null;
|
|
9
|
+
clear(): void;
|
|
10
|
+
setState(key: string, value: string): void;
|
|
11
|
+
getState(key: string): string | null;
|
|
12
|
+
removeState(key: string): void;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface CustosConfig {
|
|
2
|
+
clientId: string;
|
|
3
|
+
clientSecret?: string;
|
|
4
|
+
redirectUri: string;
|
|
5
|
+
apiUrl?: string;
|
|
6
|
+
scope?: string[];
|
|
7
|
+
}
|
|
8
|
+
export interface User {
|
|
9
|
+
id: string;
|
|
10
|
+
email: string;
|
|
11
|
+
name?: string;
|
|
12
|
+
picture?: string;
|
|
13
|
+
roles?: string[];
|
|
14
|
+
metadata?: Record<string, any>;
|
|
15
|
+
}
|
|
16
|
+
export interface AuthTokens {
|
|
17
|
+
accessToken: string;
|
|
18
|
+
refreshToken?: string;
|
|
19
|
+
expiresIn: number;
|
|
20
|
+
tokenType: string;
|
|
21
|
+
}
|
|
22
|
+
export interface AuthState {
|
|
23
|
+
isAuthenticated: boolean;
|
|
24
|
+
user: User | null;
|
|
25
|
+
tokens: AuthTokens | null;
|
|
26
|
+
}
|
|
27
|
+
export type AuthEventType = 'login' | 'logout' | 'token-refresh' | 'error';
|
|
28
|
+
export interface AuthEvent {
|
|
29
|
+
type: AuthEventType;
|
|
30
|
+
data?: any;
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +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"}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@isaias_pv/custos-sdk",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Official JavaScript SDK for Custos authentication",
|
|
5
|
+
"main": "dist/index.cjs.js",
|
|
6
|
+
"module": "dist/index.esm.js",
|
|
7
|
+
"browser": "dist/index.umd.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md",
|
|
12
|
+
"LICENSE"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "rollup -c",
|
|
16
|
+
"watch": "rollup -c -w",
|
|
17
|
+
"test": "jest",
|
|
18
|
+
"test:watch": "jest --watch",
|
|
19
|
+
"lint": "eslint src",
|
|
20
|
+
"format": "prettier --write \"src/**/*.ts\"",
|
|
21
|
+
"prepublishOnly": "npm run build",
|
|
22
|
+
"prepare": "npm run build"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"custos",
|
|
26
|
+
"authentication",
|
|
27
|
+
"oauth",
|
|
28
|
+
"sso",
|
|
29
|
+
"jwt",
|
|
30
|
+
"auth"
|
|
31
|
+
],
|
|
32
|
+
"author": "Alim <contact@alimzen.com>",
|
|
33
|
+
"license": "MIT",
|
|
34
|
+
"repository": {
|
|
35
|
+
"type": "git",
|
|
36
|
+
"url": "https://github.com/alimzen/custos-sdk.git"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/alimzen/custos-sdk/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://custos.alimzen.com",
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@rollup/plugin-commonjs": "^29.0.0",
|
|
44
|
+
"@rollup/plugin-node-resolve": "^16.0.3",
|
|
45
|
+
"@rollup/plugin-typescript": "^12.3.0",
|
|
46
|
+
"@types/jest": "^30.0.0",
|
|
47
|
+
"@types/node": "^25.2.3",
|
|
48
|
+
"@typescript-eslint/eslint-plugin": "^8.55.0",
|
|
49
|
+
"@typescript-eslint/parser": "^8.55.0",
|
|
50
|
+
"eslint": "^8.57.1",
|
|
51
|
+
"jest": "^30.2.0",
|
|
52
|
+
"jest-environment-jsdom": "^30.2.0",
|
|
53
|
+
"jsdom": "^28.1.0",
|
|
54
|
+
"prettier": "^3.8.1",
|
|
55
|
+
"rollup": "^2.79.2",
|
|
56
|
+
"rollup-plugin-terser": "^7.0.2",
|
|
57
|
+
"ts-jest": "^29.4.6",
|
|
58
|
+
"typescript": "^5.9.3"
|
|
59
|
+
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"tslib": "^2.8.1"
|
|
62
|
+
}
|
|
63
|
+
}
|