@alien_org/sso-sdk-core 1.0.19 → 1.0.20
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/index.cjs +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.esm.js +120 -108
- package/dist/index.umd.js +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var h=(d,e,o)=>new Promise((r,s)=>{var a=l=>{try{c(o.next(l))}catch(S){s(S)}},n=l=>{try{c(o.throw(l))}catch(S){s(S)}},c=l=>l.done?r(l.value):Promise.resolve(l.value).then(a,n);c((o=o.apply(d,e)).next())});Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const t=require("zod/v4-mini"),I=require("js-sha256"),y=t.z.object({deep_link:t.z.string(),polling_code:t.z.string(),expired_at:t.z.number()}),T=t.z.object({polling_code:t.z.string()}),j=["pending","authorized","rejected","expired"],x=t.z.enum(j),z=t.z.object({status:x,authorization_code:t.z.optional(t.z.string())}),m=t.z.object({access_token:t.z.string(),token_type:t.z.string(),expires_in:t.z.number(),id_token:t.z.string(),refresh_token:t.z.string()}),R=t.z.object({sub:t.z.string()}),A=t.z.object({iss:t.z.string(),sub:t.z.string(),aud:t.z.union([t.z.string(),t.z.array(t.z.string())]),exp:t.z.number(),iat:t.z.number(),nonce:t.z.optional(t.z.string()),auth_time:t.z.optional(t.z.number())}),E=m;function w(d){return btoa(d).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function _(d){let e=d.replace(/-/g,"+").replace(/_/g,"/");for(;e.length%4;)e+="=";return atob(e)}const v="https://sso.alien.com",P=5e3,i="alien-sso_",g=i+"refresh_token",p=i+"token_expiry",f=(d,e)=>new URL(e,d).toString(),b=t.z.object({ssoBaseUrl:t.z.url(),providerAddress:t.z.string(),pollingInterval:t.z.optional(t.z.number())}),u=class u{constructor(e){this.config=b.parse(e),this.ssoBaseUrl=this.config.ssoBaseUrl||v,this.providerAddress=this.config.providerAddress,this.pollingInterval=this.config.pollingInterval||P}generateCodeVerifier(e=128){let o;const r=typeof window!="undefined"&&window.crypto;if(r&&r.getRandomValues)o=new Uint8Array(e),r.getRandomValues(o);else{o=new Uint8Array(e);for(let a=0;a<e;a++)o[a]=Math.floor(Math.random()*256)}let s="";for(let a=0;a<o.length;a++)s+=String.fromCharCode(o[a]);return w(s)}generateCodeChallenge(e){const o=I.sha256.array(e),r=String.fromCharCode(...o);return w(r)}generateDeeplink(){return h(this,null,function*(){const e=this.generateCodeVerifier(),o=this.generateCodeChallenge(e);sessionStorage.setItem(i+"code_verifier",e);const r=new URLSearchParams({response_type:"code",response_mode:"json",client_id:this.providerAddress,scope:"openid",code_challenge:o,code_challenge_method:"S256"}),s=`${this.config.ssoBaseUrl}/oauth/authorize?${r.toString()}`,a=yield fetch(s,{method:"GET"});if(!a.ok){const c=yield a.json().catch(()=>({error:a.statusText}));throw new Error(`Authorize failed: ${c.error_description||c.error||a.statusText}`)}const n=yield a.json();return y.parse(n)})}pollAuth(e){return h(this,null,function*(){const o={polling_code:e};T.parse(o);const r=yield fetch(f(this.config.ssoBaseUrl,"/oauth/poll"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});if(!r.ok)throw new Error(`Poll failed: ${r.statusText}`);const s=yield r.json();return z.parse(s)})}exchangeToken(e){return h(this,null,function*(){const o=sessionStorage.getItem(i+"code_verifier");if(!o)throw new Error("Missing code verifier.");const r=new URLSearchParams({grant_type:"authorization_code",code:e,client_id:this.providerAddress,code_verifier:o}),s=yield fetch(f(this.config.ssoBaseUrl,"/oauth/token"),{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});if(!s.ok){const l=yield s.json().catch(()=>({error:s.statusText}));throw new Error(`Token exchange failed: ${l.error_description||l.error||s.statusText}`)}const a=yield s.json(),n=m.parse(a);localStorage.setItem(i+"access_token",n.access_token),localStorage.setItem(i+"id_token",n.id_token),localStorage.setItem(g,n.refresh_token);const c=Date.now()+n.expires_in*1e3;return localStorage.setItem(p,c.toString()),sessionStorage.removeItem(i+"code_verifier"),n})}verifyAuth(){return h(this,null,function*(){return this.withAutoRefresh(()=>h(this,null,function*(){const e=this.getAccessToken();if(!e)return null;const o=yield fetch(f(this.config.ssoBaseUrl,"/oauth/userinfo"),{method:"GET",headers:{Authorization:`Bearer ${e}`}});if(!o.ok){if(o.status===401){const s=new Error("Unauthorized");throw s.response={status:401},s}return null}const r=yield o.json();return R.parse(r)}))})}getAccessToken(){return localStorage.getItem(i+"access_token")}getIdToken(){return localStorage.getItem(i+"id_token")}getAuthData(){const e=this.getIdToken()||this.getAccessToken();if(!e)return null;const o=e.split(".");if(o.length!==3)return null;let r;try{const n=_(o[0]);r=JSON.parse(n)}catch(n){return null}if(r.alg!=="RS256"||r.typ!=="JWT")return null;let s;try{const n=JSON.parse(_(o[1]));s=A.parse(n)}catch(n){return null}return(Array.isArray(s.aud)?s.aud:[s.aud]).includes(this.providerAddress)?s:null}getSubject(){const e=this.getAuthData();return(e==null?void 0:e.sub)||null}isTokenExpired(){const e=this.getAuthData();return e?Date.now()/1e3>e.exp:!0}logout(){localStorage.removeItem(i+"access_token"),localStorage.removeItem(i+"id_token"),localStorage.removeItem(g),localStorage.removeItem(p),sessionStorage.removeItem(i+"code_verifier")}getRefreshToken(){return localStorage.getItem(g)}hasRefreshToken(){return!!this.getRefreshToken()}isAccessTokenExpired(){const e=localStorage.getItem(p);if(!e)return!0;const o=parseInt(e,10),r=Date.now(),s=300*1e3;return r>=o-s}refreshAccessToken(){return h(this,null,function*(){return u.refreshPromise||(u.refreshPromise=this.doRefreshAccessToken().finally(()=>{u.refreshPromise=null})),u.refreshPromise})}doRefreshAccessToken(){return h(this,null,function*(){const e=this.getRefreshToken();if(!e)throw new Error("No refresh token available");const o=new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:this.providerAddress}),r=yield fetch(f(this.config.ssoBaseUrl,"/oauth/token"),{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:o.toString()});if(!r.ok){const c=yield r.json().catch(()=>({error:r.statusText}));throw this.logout(),new Error(`Token refresh failed: ${c.error_description||c.error||r.statusText}`)}const s=yield r.json(),a=m.parse(s);localStorage.setItem(i+"access_token",a.access_token),localStorage.setItem(i+"id_token",a.id_token),localStorage.setItem(g,a.refresh_token);const n=Date.now()+a.expires_in*1e3;return localStorage.setItem(p,n.toString()),a})}withAutoRefresh(e,o=1){return h(this,null,function*(){var r,s,a;try{return yield e()}catch(n){if((((r=n==null?void 0:n.response)==null?void 0:r.status)===401||((s=n==null?void 0:n.message)==null?void 0:s.includes("401"))||((a=n==null?void 0:n.message)==null?void 0:a.includes("Unauthorized")))&&o>0&&this.hasRefreshToken())try{return yield this.refreshAccessToken(),yield e()}catch(l){throw n}throw n}})}};u.refreshPromise=null;let k=u;exports.AlienSsoClient=k;exports.AlienSsoClientSchema=b;exports.AuthorizeResponseSchema=y;exports.ExchangeCodeResponseSchema=E;exports.PollRequestSchema=T;exports.PollResponseSchema=z;exports.TokenInfoSchema=A;exports.TokenResponseSchema=m;exports.UserInfoResponseSchema=R;
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export declare class AlienSsoClient {
|
|
|
5
5
|
readonly pollingInterval: number;
|
|
6
6
|
readonly ssoBaseUrl: string;
|
|
7
7
|
readonly providerAddress: string;
|
|
8
|
+
private static refreshPromise;
|
|
8
9
|
constructor(config: AlienSsoClientConfig);
|
|
9
10
|
private generateCodeVerifier;
|
|
10
11
|
private generateCodeChallenge;
|
|
@@ -70,8 +71,13 @@ export declare class AlienSsoClient {
|
|
|
70
71
|
/**
|
|
71
72
|
* Refreshes the access token using the stored refresh token
|
|
72
73
|
* POST /oauth/token with grant_type=refresh_token
|
|
74
|
+
* Uses singleton pattern to prevent concurrent refresh requests (race condition)
|
|
73
75
|
*/
|
|
74
76
|
refreshAccessToken(): Promise<TokenResponse>;
|
|
77
|
+
/**
|
|
78
|
+
* Internal method that performs the actual token refresh
|
|
79
|
+
*/
|
|
80
|
+
private doRefreshAccessToken;
|
|
75
81
|
/**
|
|
76
82
|
* Executes a function that makes an authenticated request
|
|
77
83
|
* Automatically refreshes token and retries on 401 error
|
package/dist/index.esm.js
CHANGED
|
@@ -1,39 +1,39 @@
|
|
|
1
|
-
var
|
|
1
|
+
var h = (u, e, r) => new Promise((o, s) => {
|
|
2
2
|
var a = (l) => {
|
|
3
3
|
try {
|
|
4
|
-
c(
|
|
5
|
-
} catch (
|
|
6
|
-
s(
|
|
4
|
+
c(r.next(l));
|
|
5
|
+
} catch (m) {
|
|
6
|
+
s(m);
|
|
7
7
|
}
|
|
8
8
|
}, n = (l) => {
|
|
9
9
|
try {
|
|
10
|
-
c(
|
|
11
|
-
} catch (
|
|
12
|
-
s(
|
|
10
|
+
c(r.throw(l));
|
|
11
|
+
} catch (m) {
|
|
12
|
+
s(m);
|
|
13
13
|
}
|
|
14
|
-
}, c = (l) => l.done ?
|
|
15
|
-
c((
|
|
14
|
+
}, c = (l) => l.done ? o(l.value) : Promise.resolve(l.value).then(a, n);
|
|
15
|
+
c((r = r.apply(u, e)).next());
|
|
16
16
|
});
|
|
17
17
|
import { z as t } from "zod/v4-mini";
|
|
18
|
-
import { sha256 as
|
|
19
|
-
const
|
|
18
|
+
import { sha256 as y } from "js-sha256";
|
|
19
|
+
const T = t.object({
|
|
20
20
|
deep_link: t.string(),
|
|
21
21
|
polling_code: t.string(),
|
|
22
22
|
expired_at: t.number()
|
|
23
|
-
}),
|
|
23
|
+
}), b = t.object({
|
|
24
24
|
polling_code: t.string()
|
|
25
|
-
}),
|
|
26
|
-
status:
|
|
25
|
+
}), A = ["pending", "authorized", "rejected", "expired"], I = t.enum(A), R = t.object({
|
|
26
|
+
status: I,
|
|
27
27
|
authorization_code: t.optional(t.string())
|
|
28
|
-
}),
|
|
28
|
+
}), k = t.object({
|
|
29
29
|
access_token: t.string(),
|
|
30
30
|
token_type: t.string(),
|
|
31
31
|
expires_in: t.number(),
|
|
32
32
|
id_token: t.string(),
|
|
33
33
|
refresh_token: t.string()
|
|
34
|
-
}),
|
|
34
|
+
}), x = t.object({
|
|
35
35
|
sub: t.string()
|
|
36
|
-
}),
|
|
36
|
+
}), j = t.object({
|
|
37
37
|
iss: t.string(),
|
|
38
38
|
sub: t.string(),
|
|
39
39
|
aud: t.union([t.string(), t.array(t.string())]),
|
|
@@ -41,60 +41,59 @@ const w = t.object({
|
|
|
41
41
|
iat: t.number(),
|
|
42
42
|
nonce: t.optional(t.string()),
|
|
43
43
|
auth_time: t.optional(t.number())
|
|
44
|
-
}),
|
|
45
|
-
function
|
|
46
|
-
return btoa(
|
|
44
|
+
}), z = k;
|
|
45
|
+
function w(u) {
|
|
46
|
+
return btoa(u).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
|
|
47
47
|
}
|
|
48
|
-
function
|
|
49
|
-
let e =
|
|
48
|
+
function _(u) {
|
|
49
|
+
let e = u.replace(/-/g, "+").replace(/_/g, "/");
|
|
50
50
|
for (; e.length % 4; )
|
|
51
51
|
e += "=";
|
|
52
52
|
return atob(e);
|
|
53
53
|
}
|
|
54
|
-
const
|
|
54
|
+
const E = "https://sso.alien.com", v = 5e3, i = "alien-sso_", g = i + "refresh_token", p = i + "token_expiry", f = (u, e) => new URL(e, u).toString(), U = t.object({
|
|
55
55
|
ssoBaseUrl: t.url(),
|
|
56
56
|
providerAddress: t.string(),
|
|
57
57
|
pollingInterval: t.optional(t.number())
|
|
58
|
-
})
|
|
59
|
-
class O {
|
|
58
|
+
}), d = class d {
|
|
60
59
|
constructor(e) {
|
|
61
|
-
this.config =
|
|
60
|
+
this.config = U.parse(e), this.ssoBaseUrl = this.config.ssoBaseUrl || E, this.providerAddress = this.config.providerAddress, this.pollingInterval = this.config.pollingInterval || v;
|
|
62
61
|
}
|
|
63
62
|
generateCodeVerifier(e = 128) {
|
|
64
|
-
let
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
-
|
|
63
|
+
let r;
|
|
64
|
+
const o = typeof window != "undefined" && window.crypto;
|
|
65
|
+
if (o && o.getRandomValues)
|
|
66
|
+
r = new Uint8Array(e), o.getRandomValues(r);
|
|
68
67
|
else {
|
|
69
|
-
|
|
68
|
+
r = new Uint8Array(e);
|
|
70
69
|
for (let a = 0; a < e; a++)
|
|
71
|
-
|
|
70
|
+
r[a] = Math.floor(Math.random() * 256);
|
|
72
71
|
}
|
|
73
72
|
let s = "";
|
|
74
|
-
for (let a = 0; a <
|
|
75
|
-
s += String.fromCharCode(
|
|
76
|
-
return
|
|
73
|
+
for (let a = 0; a < r.length; a++)
|
|
74
|
+
s += String.fromCharCode(r[a]);
|
|
75
|
+
return w(s);
|
|
77
76
|
}
|
|
78
77
|
generateCodeChallenge(e) {
|
|
79
|
-
const
|
|
80
|
-
return
|
|
78
|
+
const r = y.array(e), o = String.fromCharCode(...r);
|
|
79
|
+
return w(o);
|
|
81
80
|
}
|
|
82
81
|
/**
|
|
83
82
|
* Initiates OAuth2 authorization flow with response_mode=json for SPA
|
|
84
83
|
* GET /oauth/authorize?response_type=code&response_mode=json&...
|
|
85
84
|
*/
|
|
86
85
|
generateDeeplink() {
|
|
87
|
-
return
|
|
88
|
-
const e = this.generateCodeVerifier(),
|
|
86
|
+
return h(this, null, function* () {
|
|
87
|
+
const e = this.generateCodeVerifier(), r = this.generateCodeChallenge(e);
|
|
89
88
|
sessionStorage.setItem(i + "code_verifier", e);
|
|
90
|
-
const
|
|
89
|
+
const o = new URLSearchParams({
|
|
91
90
|
response_type: "code",
|
|
92
91
|
response_mode: "json",
|
|
93
92
|
client_id: this.providerAddress,
|
|
94
93
|
scope: "openid",
|
|
95
|
-
code_challenge:
|
|
94
|
+
code_challenge: r,
|
|
96
95
|
code_challenge_method: "S256"
|
|
97
|
-
}), s = `${this.config.ssoBaseUrl}/oauth/authorize?${
|
|
96
|
+
}), s = `${this.config.ssoBaseUrl}/oauth/authorize?${o.toString()}`, a = yield fetch(s, {
|
|
98
97
|
method: "GET"
|
|
99
98
|
});
|
|
100
99
|
if (!a.ok) {
|
|
@@ -102,7 +101,7 @@ class O {
|
|
|
102
101
|
throw new Error(`Authorize failed: ${c.error_description || c.error || a.statusText}`);
|
|
103
102
|
}
|
|
104
103
|
const n = yield a.json();
|
|
105
|
-
return
|
|
104
|
+
return T.parse(n);
|
|
106
105
|
});
|
|
107
106
|
}
|
|
108
107
|
/**
|
|
@@ -110,22 +109,22 @@ class O {
|
|
|
110
109
|
* POST /oauth/poll
|
|
111
110
|
*/
|
|
112
111
|
pollAuth(e) {
|
|
113
|
-
return
|
|
114
|
-
const
|
|
112
|
+
return h(this, null, function* () {
|
|
113
|
+
const r = {
|
|
115
114
|
polling_code: e
|
|
116
115
|
};
|
|
117
|
-
|
|
118
|
-
const
|
|
116
|
+
b.parse(r);
|
|
117
|
+
const o = yield fetch(f(this.config.ssoBaseUrl, "/oauth/poll"), {
|
|
119
118
|
method: "POST",
|
|
120
119
|
headers: {
|
|
121
120
|
"Content-Type": "application/json"
|
|
122
121
|
},
|
|
123
|
-
body: JSON.stringify(
|
|
122
|
+
body: JSON.stringify(r)
|
|
124
123
|
});
|
|
125
|
-
if (!
|
|
126
|
-
throw new Error(`Poll failed: ${
|
|
127
|
-
const s = yield
|
|
128
|
-
return
|
|
124
|
+
if (!o.ok)
|
|
125
|
+
throw new Error(`Poll failed: ${o.statusText}`);
|
|
126
|
+
const s = yield o.json();
|
|
127
|
+
return R.parse(s);
|
|
129
128
|
});
|
|
130
129
|
}
|
|
131
130
|
/**
|
|
@@ -134,32 +133,32 @@ class O {
|
|
|
134
133
|
* Returns both access_token and id_token
|
|
135
134
|
*/
|
|
136
135
|
exchangeToken(e) {
|
|
137
|
-
return
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
140
|
-
const
|
|
136
|
+
return h(this, null, function* () {
|
|
137
|
+
const r = sessionStorage.getItem(i + "code_verifier");
|
|
138
|
+
if (!r) throw new Error("Missing code verifier.");
|
|
139
|
+
const o = new URLSearchParams({
|
|
141
140
|
grant_type: "authorization_code",
|
|
142
141
|
code: e,
|
|
143
142
|
client_id: this.providerAddress,
|
|
144
|
-
code_verifier:
|
|
143
|
+
code_verifier: r
|
|
145
144
|
}), s = yield fetch(
|
|
146
|
-
|
|
145
|
+
f(this.config.ssoBaseUrl, "/oauth/token"),
|
|
147
146
|
{
|
|
148
147
|
method: "POST",
|
|
149
148
|
headers: {
|
|
150
149
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
151
150
|
},
|
|
152
|
-
body:
|
|
151
|
+
body: o.toString()
|
|
153
152
|
}
|
|
154
153
|
);
|
|
155
154
|
if (!s.ok) {
|
|
156
155
|
const l = yield s.json().catch(() => ({ error: s.statusText }));
|
|
157
156
|
throw new Error(`Token exchange failed: ${l.error_description || l.error || s.statusText}`);
|
|
158
157
|
}
|
|
159
|
-
const a = yield s.json(), n =
|
|
160
|
-
localStorage.setItem(i + "access_token", n.access_token), localStorage.setItem(i + "id_token", n.id_token), localStorage.setItem(
|
|
158
|
+
const a = yield s.json(), n = k.parse(a);
|
|
159
|
+
localStorage.setItem(i + "access_token", n.access_token), localStorage.setItem(i + "id_token", n.id_token), localStorage.setItem(g, n.refresh_token);
|
|
161
160
|
const c = Date.now() + n.expires_in * 1e3;
|
|
162
|
-
return localStorage.setItem(
|
|
161
|
+
return localStorage.setItem(p, c.toString()), sessionStorage.removeItem(i + "code_verifier"), n;
|
|
163
162
|
});
|
|
164
163
|
}
|
|
165
164
|
/**
|
|
@@ -168,13 +167,13 @@ class O {
|
|
|
168
167
|
* Automatically refreshes token on 401 if refresh token is available
|
|
169
168
|
*/
|
|
170
169
|
verifyAuth() {
|
|
171
|
-
return
|
|
172
|
-
return this.withAutoRefresh(() =>
|
|
170
|
+
return h(this, null, function* () {
|
|
171
|
+
return this.withAutoRefresh(() => h(this, null, function* () {
|
|
173
172
|
const e = this.getAccessToken();
|
|
174
173
|
if (!e)
|
|
175
174
|
return null;
|
|
176
|
-
const
|
|
177
|
-
|
|
175
|
+
const r = yield fetch(
|
|
176
|
+
f(this.config.ssoBaseUrl, "/oauth/userinfo"),
|
|
178
177
|
{
|
|
179
178
|
method: "GET",
|
|
180
179
|
headers: {
|
|
@@ -182,15 +181,15 @@ class O {
|
|
|
182
181
|
}
|
|
183
182
|
}
|
|
184
183
|
);
|
|
185
|
-
if (!
|
|
186
|
-
if (
|
|
184
|
+
if (!r.ok) {
|
|
185
|
+
if (r.status === 401) {
|
|
187
186
|
const s = new Error("Unauthorized");
|
|
188
187
|
throw s.response = { status: 401 }, s;
|
|
189
188
|
}
|
|
190
189
|
return null;
|
|
191
190
|
}
|
|
192
|
-
const
|
|
193
|
-
return
|
|
191
|
+
const o = yield r.json();
|
|
192
|
+
return x.parse(o);
|
|
194
193
|
}));
|
|
195
194
|
});
|
|
196
195
|
}
|
|
@@ -213,22 +212,22 @@ class O {
|
|
|
213
212
|
getAuthData() {
|
|
214
213
|
const e = this.getIdToken() || this.getAccessToken();
|
|
215
214
|
if (!e) return null;
|
|
216
|
-
const
|
|
217
|
-
if (
|
|
215
|
+
const r = e.split(".");
|
|
216
|
+
if (r.length !== 3)
|
|
218
217
|
return null;
|
|
219
|
-
let
|
|
218
|
+
let o;
|
|
220
219
|
try {
|
|
221
|
-
const n =
|
|
222
|
-
|
|
220
|
+
const n = _(r[0]);
|
|
221
|
+
o = JSON.parse(n);
|
|
223
222
|
} catch (n) {
|
|
224
223
|
return null;
|
|
225
224
|
}
|
|
226
|
-
if (
|
|
225
|
+
if (o.alg !== "RS256" || o.typ !== "JWT")
|
|
227
226
|
return null;
|
|
228
227
|
let s;
|
|
229
228
|
try {
|
|
230
|
-
const n = JSON.parse(
|
|
231
|
-
s =
|
|
229
|
+
const n = JSON.parse(_(r[1]));
|
|
230
|
+
s = j.parse(n);
|
|
232
231
|
} catch (n) {
|
|
233
232
|
return null;
|
|
234
233
|
}
|
|
@@ -252,13 +251,13 @@ class O {
|
|
|
252
251
|
* Clears all stored authentication data
|
|
253
252
|
*/
|
|
254
253
|
logout() {
|
|
255
|
-
localStorage.removeItem(i + "access_token"), localStorage.removeItem(i + "id_token"), localStorage.removeItem(
|
|
254
|
+
localStorage.removeItem(i + "access_token"), localStorage.removeItem(i + "id_token"), localStorage.removeItem(g), localStorage.removeItem(p), sessionStorage.removeItem(i + "code_verifier");
|
|
256
255
|
}
|
|
257
256
|
/**
|
|
258
257
|
* Gets stored refresh token
|
|
259
258
|
*/
|
|
260
259
|
getRefreshToken() {
|
|
261
|
-
return localStorage.getItem(
|
|
260
|
+
return localStorage.getItem(g);
|
|
262
261
|
}
|
|
263
262
|
/**
|
|
264
263
|
* Checks if a refresh token is available
|
|
@@ -270,55 +269,66 @@ class O {
|
|
|
270
269
|
* Checks if the access token is expired or will expire soon (within 5 minutes)
|
|
271
270
|
*/
|
|
272
271
|
isAccessTokenExpired() {
|
|
273
|
-
const e = localStorage.getItem(
|
|
272
|
+
const e = localStorage.getItem(p);
|
|
274
273
|
if (!e) return !0;
|
|
275
|
-
const
|
|
276
|
-
return
|
|
274
|
+
const r = parseInt(e, 10), o = Date.now(), s = 300 * 1e3;
|
|
275
|
+
return o >= r - s;
|
|
277
276
|
}
|
|
278
277
|
/**
|
|
279
278
|
* Refreshes the access token using the stored refresh token
|
|
280
279
|
* POST /oauth/token with grant_type=refresh_token
|
|
280
|
+
* Uses singleton pattern to prevent concurrent refresh requests (race condition)
|
|
281
281
|
*/
|
|
282
282
|
refreshAccessToken() {
|
|
283
|
-
return
|
|
283
|
+
return h(this, null, function* () {
|
|
284
|
+
return d.refreshPromise || (d.refreshPromise = this.doRefreshAccessToken().finally(() => {
|
|
285
|
+
d.refreshPromise = null;
|
|
286
|
+
})), d.refreshPromise;
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Internal method that performs the actual token refresh
|
|
291
|
+
*/
|
|
292
|
+
doRefreshAccessToken() {
|
|
293
|
+
return h(this, null, function* () {
|
|
284
294
|
const e = this.getRefreshToken();
|
|
285
295
|
if (!e)
|
|
286
296
|
throw new Error("No refresh token available");
|
|
287
|
-
const
|
|
297
|
+
const r = new URLSearchParams({
|
|
288
298
|
grant_type: "refresh_token",
|
|
289
299
|
refresh_token: e,
|
|
290
300
|
client_id: this.providerAddress
|
|
291
|
-
}),
|
|
292
|
-
|
|
301
|
+
}), o = yield fetch(
|
|
302
|
+
f(this.config.ssoBaseUrl, "/oauth/token"),
|
|
293
303
|
{
|
|
294
304
|
method: "POST",
|
|
295
305
|
headers: {
|
|
296
306
|
"Content-Type": "application/x-www-form-urlencoded"
|
|
297
307
|
},
|
|
298
|
-
body:
|
|
308
|
+
body: r.toString()
|
|
299
309
|
}
|
|
300
310
|
);
|
|
301
|
-
if (!
|
|
302
|
-
const c = yield
|
|
303
|
-
throw this.logout(), new Error(`Token refresh failed: ${c.error_description || c.error ||
|
|
311
|
+
if (!o.ok) {
|
|
312
|
+
const c = yield o.json().catch(() => ({ error: o.statusText }));
|
|
313
|
+
throw this.logout(), new Error(`Token refresh failed: ${c.error_description || c.error || o.statusText}`);
|
|
304
314
|
}
|
|
305
|
-
const s = yield
|
|
306
|
-
localStorage.setItem(i + "access_token", a.access_token), localStorage.setItem(i + "id_token", a.id_token), localStorage.setItem(
|
|
315
|
+
const s = yield o.json(), a = k.parse(s);
|
|
316
|
+
localStorage.setItem(i + "access_token", a.access_token), localStorage.setItem(i + "id_token", a.id_token), localStorage.setItem(g, a.refresh_token);
|
|
307
317
|
const n = Date.now() + a.expires_in * 1e3;
|
|
308
|
-
return localStorage.setItem(
|
|
318
|
+
return localStorage.setItem(p, n.toString()), a;
|
|
309
319
|
});
|
|
310
320
|
}
|
|
311
321
|
/**
|
|
312
322
|
* Executes a function that makes an authenticated request
|
|
313
323
|
* Automatically refreshes token and retries on 401 error
|
|
314
324
|
*/
|
|
315
|
-
withAutoRefresh(e,
|
|
316
|
-
return
|
|
317
|
-
var
|
|
325
|
+
withAutoRefresh(e, r = 1) {
|
|
326
|
+
return h(this, null, function* () {
|
|
327
|
+
var o, s, a;
|
|
318
328
|
try {
|
|
319
329
|
return yield e();
|
|
320
330
|
} catch (n) {
|
|
321
|
-
if ((((
|
|
331
|
+
if ((((o = n == null ? void 0 : n.response) == null ? void 0 : o.status) === 401 || ((s = n == null ? void 0 : n.message) == null ? void 0 : s.includes("401")) || ((a = n == null ? void 0 : n.message) == null ? void 0 : a.includes("Unauthorized"))) && r > 0 && this.hasRefreshToken())
|
|
322
332
|
try {
|
|
323
333
|
return yield this.refreshAccessToken(), yield e();
|
|
324
334
|
} catch (l) {
|
|
@@ -328,15 +338,17 @@ class O {
|
|
|
328
338
|
}
|
|
329
339
|
});
|
|
330
340
|
}
|
|
331
|
-
}
|
|
341
|
+
};
|
|
342
|
+
d.refreshPromise = null;
|
|
343
|
+
let S = d;
|
|
332
344
|
export {
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
345
|
+
S as AlienSsoClient,
|
|
346
|
+
U as AlienSsoClientSchema,
|
|
347
|
+
T as AuthorizeResponseSchema,
|
|
348
|
+
z as ExchangeCodeResponseSchema,
|
|
349
|
+
b as PollRequestSchema,
|
|
350
|
+
R as PollResponseSchema,
|
|
351
|
+
j as TokenInfoSchema,
|
|
352
|
+
k as TokenResponseSchema,
|
|
353
|
+
x as UserInfoResponseSchema
|
|
342
354
|
};
|
package/dist/index.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(c,t){typeof exports=="object"&&typeof module!="undefined"?t(exports,require("zod/v4-mini"),require("js-sha256")):typeof define=="function"&&define.amd?define(["exports","zod/v4-mini","js-sha256"],t):(c=typeof globalThis!="undefined"?globalThis:c||self,t(c.AlienSsoCore={},c.Zod,c.jsSha256))})(this,(function(c,t,
|
|
1
|
+
(function(c,t){typeof exports=="object"&&typeof module!="undefined"?t(exports,require("zod/v4-mini"),require("js-sha256")):typeof define=="function"&&define.amd?define(["exports","zod/v4-mini","js-sha256"],t):(c=typeof globalThis!="undefined"?globalThis:c||self,t(c.AlienSsoCore={},c.Zod,c.jsSha256))})(this,(function(c,t,g){"use strict";var h=(c,t,g)=>new Promise((k,m)=>{var T=l=>{try{f(g.next(l))}catch(p){m(p)}},z=l=>{try{f(g.throw(l))}catch(p){m(p)}},f=l=>l.done?k(l.value):Promise.resolve(l.value).then(T,z);f((g=g.apply(c,t)).next())});const k=t.z.object({deep_link:t.z.string(),polling_code:t.z.string(),expired_at:t.z.number()}),m=t.z.object({polling_code:t.z.string()}),T=["pending","authorized","rejected","expired"],z=t.z.enum(T),f=t.z.object({status:z,authorization_code:t.z.optional(t.z.string())}),l=t.z.object({access_token:t.z.string(),token_type:t.z.string(),expires_in:t.z.number(),id_token:t.z.string(),refresh_token:t.z.string()}),p=t.z.object({sub:t.z.string()}),b=t.z.object({iss:t.z.string(),sub:t.z.string(),aud:t.z.union([t.z.string(),t.z.array(t.z.string())]),exp:t.z.number(),iat:t.z.number(),nonce:t.z.optional(t.z.string()),auth_time:t.z.optional(t.z.number())}),P=l;function I(S){return btoa(S).replace(/\+/g,"-").replace(/\//g,"_").replace(/=/g,"")}function j(S){let e=S.replace(/-/g,"+").replace(/_/g,"/");for(;e.length%4;)e+="=";return atob(e)}const x="https://sso.alien.com",U=5e3,i="alien-sso_",w=i+"refresh_token",_=i+"token_expiry",y=(S,e)=>new URL(e,S).toString(),E=t.z.object({ssoBaseUrl:t.z.url(),providerAddress:t.z.string(),pollingInterval:t.z.optional(t.z.number())}),d=class d{constructor(e){this.config=E.parse(e),this.ssoBaseUrl=this.config.ssoBaseUrl||x,this.providerAddress=this.config.providerAddress,this.pollingInterval=this.config.pollingInterval||U}generateCodeVerifier(e=128){let o;const r=typeof window!="undefined"&&window.crypto;if(r&&r.getRandomValues)o=new Uint8Array(e),r.getRandomValues(o);else{o=new Uint8Array(e);for(let a=0;a<e;a++)o[a]=Math.floor(Math.random()*256)}let s="";for(let a=0;a<o.length;a++)s+=String.fromCharCode(o[a]);return I(s)}generateCodeChallenge(e){const o=g.sha256.array(e),r=String.fromCharCode(...o);return I(r)}generateDeeplink(){return h(this,null,function*(){const e=this.generateCodeVerifier(),o=this.generateCodeChallenge(e);sessionStorage.setItem(i+"code_verifier",e);const r=new URLSearchParams({response_type:"code",response_mode:"json",client_id:this.providerAddress,scope:"openid",code_challenge:o,code_challenge_method:"S256"}),s=`${this.config.ssoBaseUrl}/oauth/authorize?${r.toString()}`,a=yield fetch(s,{method:"GET"});if(!a.ok){const u=yield a.json().catch(()=>({error:a.statusText}));throw new Error(`Authorize failed: ${u.error_description||u.error||a.statusText}`)}const n=yield a.json();return k.parse(n)})}pollAuth(e){return h(this,null,function*(){const o={polling_code:e};m.parse(o);const r=yield fetch(y(this.config.ssoBaseUrl,"/oauth/poll"),{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(o)});if(!r.ok)throw new Error(`Poll failed: ${r.statusText}`);const s=yield r.json();return f.parse(s)})}exchangeToken(e){return h(this,null,function*(){const o=sessionStorage.getItem(i+"code_verifier");if(!o)throw new Error("Missing code verifier.");const r=new URLSearchParams({grant_type:"authorization_code",code:e,client_id:this.providerAddress,code_verifier:o}),s=yield fetch(y(this.config.ssoBaseUrl,"/oauth/token"),{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:r.toString()});if(!s.ok){const R=yield s.json().catch(()=>({error:s.statusText}));throw new Error(`Token exchange failed: ${R.error_description||R.error||s.statusText}`)}const a=yield s.json(),n=l.parse(a);localStorage.setItem(i+"access_token",n.access_token),localStorage.setItem(i+"id_token",n.id_token),localStorage.setItem(w,n.refresh_token);const u=Date.now()+n.expires_in*1e3;return localStorage.setItem(_,u.toString()),sessionStorage.removeItem(i+"code_verifier"),n})}verifyAuth(){return h(this,null,function*(){return this.withAutoRefresh(()=>h(this,null,function*(){const e=this.getAccessToken();if(!e)return null;const o=yield fetch(y(this.config.ssoBaseUrl,"/oauth/userinfo"),{method:"GET",headers:{Authorization:`Bearer ${e}`}});if(!o.ok){if(o.status===401){const s=new Error("Unauthorized");throw s.response={status:401},s}return null}const r=yield o.json();return p.parse(r)}))})}getAccessToken(){return localStorage.getItem(i+"access_token")}getIdToken(){return localStorage.getItem(i+"id_token")}getAuthData(){const e=this.getIdToken()||this.getAccessToken();if(!e)return null;const o=e.split(".");if(o.length!==3)return null;let r;try{const n=j(o[0]);r=JSON.parse(n)}catch(n){return null}if(r.alg!=="RS256"||r.typ!=="JWT")return null;let s;try{const n=JSON.parse(j(o[1]));s=b.parse(n)}catch(n){return null}return(Array.isArray(s.aud)?s.aud:[s.aud]).includes(this.providerAddress)?s:null}getSubject(){const e=this.getAuthData();return(e==null?void 0:e.sub)||null}isTokenExpired(){const e=this.getAuthData();return e?Date.now()/1e3>e.exp:!0}logout(){localStorage.removeItem(i+"access_token"),localStorage.removeItem(i+"id_token"),localStorage.removeItem(w),localStorage.removeItem(_),sessionStorage.removeItem(i+"code_verifier")}getRefreshToken(){return localStorage.getItem(w)}hasRefreshToken(){return!!this.getRefreshToken()}isAccessTokenExpired(){const e=localStorage.getItem(_);if(!e)return!0;const o=parseInt(e,10),r=Date.now(),s=300*1e3;return r>=o-s}refreshAccessToken(){return h(this,null,function*(){return d.refreshPromise||(d.refreshPromise=this.doRefreshAccessToken().finally(()=>{d.refreshPromise=null})),d.refreshPromise})}doRefreshAccessToken(){return h(this,null,function*(){const e=this.getRefreshToken();if(!e)throw new Error("No refresh token available");const o=new URLSearchParams({grant_type:"refresh_token",refresh_token:e,client_id:this.providerAddress}),r=yield fetch(y(this.config.ssoBaseUrl,"/oauth/token"),{method:"POST",headers:{"Content-Type":"application/x-www-form-urlencoded"},body:o.toString()});if(!r.ok){const u=yield r.json().catch(()=>({error:r.statusText}));throw this.logout(),new Error(`Token refresh failed: ${u.error_description||u.error||r.statusText}`)}const s=yield r.json(),a=l.parse(s);localStorage.setItem(i+"access_token",a.access_token),localStorage.setItem(i+"id_token",a.id_token),localStorage.setItem(w,a.refresh_token);const n=Date.now()+a.expires_in*1e3;return localStorage.setItem(_,n.toString()),a})}withAutoRefresh(e,o=1){return h(this,null,function*(){var r,s,a;try{return yield e()}catch(n){if((((r=n==null?void 0:n.response)==null?void 0:r.status)===401||((s=n==null?void 0:n.message)==null?void 0:s.includes("401"))||((a=n==null?void 0:n.message)==null?void 0:a.includes("Unauthorized")))&&o>0&&this.hasRefreshToken())try{return yield this.refreshAccessToken(),yield e()}catch(R){throw n}throw n}})}};d.refreshPromise=null;let A=d;c.AlienSsoClient=A,c.AlienSsoClientSchema=E,c.AuthorizeResponseSchema=k,c.ExchangeCodeResponseSchema=P,c.PollRequestSchema=m,c.PollResponseSchema=f,c.TokenInfoSchema=b,c.TokenResponseSchema=l,c.UserInfoResponseSchema=p,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})}));
|