@gh-platform/auth-sdk 1.0.10 → 1.0.12
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/Makefile +63 -0
- package/dist/auth-sdk.es.js +38 -5
- package/dist/auth-sdk.min.js +1 -1
- package/dist/auth-sdk.umd.js +1 -1
- package/dist/index.d.ts +1 -1
- package/package.json +20 -20
- package/src/index.d.ts +1 -1
- package/src/middleware.js +66 -5
package/Makefile
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
SHELL := /usr/bin/env bash
|
|
2
|
+
.SHELLFLAGS := -euo pipefail -c
|
|
3
|
+
|
|
4
|
+
PACKAGE_JSON := package.json
|
|
5
|
+
VERSION := $(shell jq -r '.version' $(PACKAGE_JSON))
|
|
6
|
+
LATEST_TAG := $(shell git tag --sort=-v:refname | head -n 1)
|
|
7
|
+
|
|
8
|
+
define check_jq
|
|
9
|
+
@if ! command -v jq >/dev/null 2>&1; then \
|
|
10
|
+
echo "❌ jq is not installed."; \
|
|
11
|
+
echo ""; \
|
|
12
|
+
echo "👉 Install jq with:"; \
|
|
13
|
+
echo " macOS (Homebrew): brew install jq"; \
|
|
14
|
+
echo " Ubuntu/Debian: sudo apt install jq"; \
|
|
15
|
+
echo " Alpine Linux: apk add jq"; \
|
|
16
|
+
echo " Fedora/RHEL: sudo dnf install jq"; \
|
|
17
|
+
echo " Arch Linux: sudo pacman -S jq"; \
|
|
18
|
+
echo " Windows (Chocolatey): choco install jq"; \
|
|
19
|
+
echo " Windows (Scoop): scoop install jq"; \
|
|
20
|
+
exit 1; \
|
|
21
|
+
fi
|
|
22
|
+
endef
|
|
23
|
+
|
|
24
|
+
define bump_version
|
|
25
|
+
$(call check_jq)
|
|
26
|
+
|
|
27
|
+
@echo "📦 Package: $$(jq -r '.name' $(PACKAGE_JSON))"
|
|
28
|
+
@echo "🔖 Current version: $(VERSION)"
|
|
29
|
+
|
|
30
|
+
@if [[ -z "$(VERSION)" ]]; then \
|
|
31
|
+
echo "❌ Cannot read version from package.json"; \
|
|
32
|
+
exit 1; \
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
@PREFIX=$$(echo "$(LATEST_TAG)" | sed -n 's/^\(v\).*/\1/p'); \
|
|
36
|
+
NEW_VERSION=$$(echo "$(VERSION)" | awk -F. '{ \
|
|
37
|
+
major=$$1; minor=$$2; patch=$$3; \
|
|
38
|
+
if ("$(1)"=="patch") patch++; \
|
|
39
|
+
else if ("$(1)"=="minor") { minor++; patch=0; } \
|
|
40
|
+
else if ("$(1)"=="major") { major++; minor=0; patch=0; } \
|
|
41
|
+
printf "%d.%d.%d", major, minor, patch \
|
|
42
|
+
}'); \
|
|
43
|
+
NEW_TAG="$$PREFIX$$NEW_VERSION"; \
|
|
44
|
+
echo "🚀 New version: $$NEW_VERSION"; \
|
|
45
|
+
echo "🏷 New tag: $$NEW_TAG"; \
|
|
46
|
+
jq '.version="'"$$NEW_VERSION"'"' $(PACKAGE_JSON) > $(PACKAGE_JSON).tmp; \
|
|
47
|
+
mv $(PACKAGE_JSON).tmp $(PACKAGE_JSON); \
|
|
48
|
+
git add $(PACKAGE_JSON); \
|
|
49
|
+
git commit -m "chore(release): $$NEW_VERSION"; \
|
|
50
|
+
git tag $$NEW_TAG; \
|
|
51
|
+
git push origin main --tags
|
|
52
|
+
endef
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# -------- Commands --------
|
|
56
|
+
bump-patch:
|
|
57
|
+
$(call bump_version,patch)
|
|
58
|
+
|
|
59
|
+
bump-minor:
|
|
60
|
+
$(call bump_version,minor)
|
|
61
|
+
|
|
62
|
+
bump-major:
|
|
63
|
+
$(call bump_version,major)
|
package/dist/auth-sdk.es.js
CHANGED
|
@@ -147,6 +147,22 @@ class AuthFetch {
|
|
|
147
147
|
this.introspectUrl = `${introspectBaseUrl}/${introspectPath.replace(/^\//, "")}`;
|
|
148
148
|
this.storage = storage || new TokenStorage("auth", authClient.tenant || null);
|
|
149
149
|
}
|
|
150
|
+
_setAuthCookie() {
|
|
151
|
+
const maxAge = 60 * 5;
|
|
152
|
+
const parts = [
|
|
153
|
+
`auth_state=1`,
|
|
154
|
+
`Path=/`,
|
|
155
|
+
`Max-Age=${maxAge}`,
|
|
156
|
+
`SameSite=Lax`
|
|
157
|
+
];
|
|
158
|
+
if (location.protocol === "https:") {
|
|
159
|
+
parts.push("Secure");
|
|
160
|
+
}
|
|
161
|
+
document.cookie = parts.join("; ");
|
|
162
|
+
}
|
|
163
|
+
_clearAuthCookie() {
|
|
164
|
+
document.cookie = `auth_state=; Path=/; Max-Age=0`;
|
|
165
|
+
}
|
|
150
166
|
/**
|
|
151
167
|
* fetchWithProgress:
|
|
152
168
|
* - input: URL
|
|
@@ -228,14 +244,27 @@ class AuthFetch {
|
|
|
228
244
|
finalToken = this.storage.accessToken;
|
|
229
245
|
}
|
|
230
246
|
if (!finalToken) {
|
|
247
|
+
this._clearAuthCookie();
|
|
231
248
|
throw new Error("No access token available for introspection");
|
|
232
249
|
}
|
|
233
250
|
if (!this.introspectUrl) {
|
|
234
251
|
throw new Error("No introspect url config");
|
|
235
252
|
}
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
253
|
+
let res;
|
|
254
|
+
try {
|
|
255
|
+
res = await this.fetch(this.introspectUrl, {
|
|
256
|
+
method: "GET"
|
|
257
|
+
});
|
|
258
|
+
} catch (err) {
|
|
259
|
+
throw err;
|
|
260
|
+
}
|
|
261
|
+
if (res.status === 401 || res.status === 403) {
|
|
262
|
+
this._clearAuthCookie();
|
|
263
|
+
throw new Error(`Unauthorized: ${res.status}`);
|
|
264
|
+
}
|
|
265
|
+
if (res.status >= 500) {
|
|
266
|
+
throw new Error(`Auth service error: ${res.status}`);
|
|
267
|
+
}
|
|
239
268
|
if (!res.ok) {
|
|
240
269
|
const text = await res.text().catch(() => res.statusText);
|
|
241
270
|
throw new Error(`Introspect failed: ${res.status} ${text}`);
|
|
@@ -244,9 +273,13 @@ class AuthFetch {
|
|
|
244
273
|
try {
|
|
245
274
|
json = await res.json();
|
|
246
275
|
} catch (e) {
|
|
247
|
-
|
|
248
|
-
|
|
276
|
+
throw new Error("Invalid JSON response from auth service");
|
|
277
|
+
}
|
|
278
|
+
if (json.active === false) {
|
|
279
|
+
this._clearAuthCookie();
|
|
280
|
+
throw new Error("Token inactive");
|
|
249
281
|
}
|
|
282
|
+
this._setAuthCookie();
|
|
250
283
|
return json;
|
|
251
284
|
}
|
|
252
285
|
}
|
package/dist/auth-sdk.min.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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).AuthSDK={})}(this,function(e){"use strict";let t=null;function r(){return t}function s(e){t=e}class o{constructor({baseUrl:e,tenant:t=null,loginPath:r=null,refreshPath:s=null,headers:o={},storage:
|
|
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).AuthSDK={})}(this,function(e){"use strict";let t=null;function r(){return t}function s(e){t=e}class o{constructor({baseUrl:e,tenant:t=null,loginPath:r=null,refreshPath:s=null,headers:o={},storage:a=null}){if(!e)throw new Error("baseUrl is required");this.baseUrl=e.replace(/\/$/,"");const n=t?`/api/v1/${t}/auth`:"/api/v1/auth";this.loginUrl=this.baseUrl+(r||`${n}/login`),this.refreshUrl=this.baseUrl+(s||`${n}/refresh`),this.tenant=t,this.headers={"Content-Type":"application/json",...o},this.storage=a}async login(e,t,r=null,s={}){const o={identifier:e,password:t,...s};r&&(o.totp=r);const a=await fetch(this.loginUrl,{method:"POST",headers:this.headers,body:JSON.stringify(o)});if(!a.ok){const e=await a.text().catch(()=>a.statusText);throw new Error(`Login failed: ${a.status} ${e}`)}let n;try{n=await a.json()}catch(e){throw console.error("❌ JSON parse error:",e),new Error("Invalid JSON response from server")}const i=n.data||n;return this.storage&&(i.access_token&&(this.storage.accessToken=i.access_token),i.refresh_token&&(this.storage.refreshToken=i.refresh_token)),n}async refresh(e){if(r())return r();const t=(async()=>{const t=await fetch(this.refreshUrl,{method:"POST",headers:this.headers,body:JSON.stringify({refresh_token:e})});if(!t.ok){const e=await t.text().catch(()=>t.statusText);throw new Error(`Refresh failed: ${t.status} ${e}`)}let r;try{r=await t.json()}catch(e){throw console.error("❌ JSON parse error:",e),new Error("Invalid JSON response from server")}const s=r.data||r;return this.storage&&(s.access_token&&(this.storage.accessToken=s.access_token),s.refresh_token&&(this.storage.refreshToken=s.refresh_token)),r})();s(t);try{return await t}finally{s(null)}}}class a{constructor(e="auth",t=null){this.prefix=e,this.tenant=t}_key(e){return this.tenant?`${this.prefix}:${this.tenant}_${e}`:`${this.prefix}_${e}`}get accessToken(){return localStorage.getItem(this._key("access_token"))}set accessToken(e){null==e?localStorage.removeItem(this._key("access_token")):localStorage.setItem(this._key("access_token"),e)}get refreshToken(){return localStorage.getItem(this._key("refresh_token"))}set refreshToken(e){null==e?localStorage.removeItem(this._key("refresh_token")):localStorage.setItem(this._key("refresh_token"),e)}clear(){localStorage.removeItem(this._key("access_token")),localStorage.removeItem(this._key("refresh_token"))}}class n{constructor(e,t,r=null,s="introspect"){if(!t)throw new Error("introspectBaseUrl is required");this.client=e,this.introspectUrl=`${t}/${s.replace(/^\//,"")}`,this.storage=r||new a("auth",e.tenant||null)}_setAuthCookie(){const e=["auth_state=1","Path=/","Max-Age=300","SameSite=Lax"];"https:"===location.protocol&&e.push("Secure"),document.cookie=e.join("; ")}_clearAuthCookie(){document.cookie="auth_state=; Path=/; Max-Age=0"}async fetch(e,t={},r=null){const s=this.storage.accessToken,o=new Headers(t.headers||{});return s&&o.set("Authorization",`Bearer ${s}`),t.method&&"GET"!==t.method&&t.body?await this._xhrRequest(e,t,o,r):await this._fetchWithDownloadProgress(e,t,o,r)}async _fetchWithDownloadProgress(e,t,r,s){let o=await fetch(e,{...t,headers:r});if(401===o.status&&this.storage.refreshToken)try{const s=await this.client.refresh(this.storage.refreshToken);s.access_token&&(this.storage.accessToken=s.access_token),s.refresh_token&&(this.storage.refreshToken=s.refresh_token),r.set("Authorization",`Bearer ${this.storage.accessToken}`),o=await fetch(e,{...t,headers:r})}catch{throw this.storage.clear(),new Error("Unauthorized, please login again")}if(!s||!o.body)return o;const a=o.body.getReader(),n=+o.headers.get("Content-Length")||0;let i=0;const h=[];for(;;){const{done:e,value:t}=await a.read();if(e)break;h.push(t),i+=t.length,n?s(Math.round(i/n*100),i,n):s(null,i,null)}const c=new Blob(h);return new Response(c,o)}_xhrRequest(e,t,r,s){return new Promise((o,a)=>{const n=new XMLHttpRequest;n.open(t.method||"POST",e,!0);for(const[e,t]of r.entries())n.setRequestHeader(e,t);n.upload&&s&&(n.upload.onprogress=e=>{if(e.lengthComputable){const t=Math.round(e.loaded/e.total*100);s(t,e.loaded,e.total)}else s(null,e.loaded,null)}),n.onload=()=>{o(new Response(n.response,{status:n.status}))},n.onerror=()=>a(new Error("Network error")),n.send(t.body)})}async introspect(e=null){let t,r,s=e;if(this.storage&&!s&&(s=this.storage.accessToken),!s)throw this._clearAuthCookie(),new Error("No access token available for introspection");if(!this.introspectUrl)throw new Error("No introspect url config");try{t=await this.fetch(this.introspectUrl,{method:"GET"})}catch(e){throw e}if(401===t.status||403===t.status)throw this._clearAuthCookie(),new Error(`Unauthorized: ${t.status}`);if(t.status>=500)throw new Error(`Auth service error: ${t.status}`);if(!t.ok){const e=await t.text().catch(()=>t.statusText);throw new Error(`Introspect failed: ${t.status} ${e}`)}try{r=await t.json()}catch(e){throw new Error("Invalid JSON response from auth service")}if(!1===r.active)throw this._clearAuthCookie(),new Error("Token inactive");return this._setAuthCookie(),r}}const i={AuthClient:o,AuthFetch:n,TokenStorage:a};e.AuthClient=o,e.AuthFetch=n,e.TokenStorage=a,e.default=i,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
package/dist/auth-sdk.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
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).AuthSDK={})}(this,function(e){"use strict";let t=null;function r(){return t}function s(e){t=e}class o{constructor({baseUrl:e,tenant:t=null,loginPath:r=null,refreshPath:s=null,headers:o={},storage:
|
|
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).AuthSDK={})}(this,function(e){"use strict";let t=null;function r(){return t}function s(e){t=e}class o{constructor({baseUrl:e,tenant:t=null,loginPath:r=null,refreshPath:s=null,headers:o={},storage:a=null}){if(!e)throw new Error("baseUrl is required");this.baseUrl=e.replace(/\/$/,"");const n=t?`/api/v1/${t}/auth`:"/api/v1/auth";this.loginUrl=this.baseUrl+(r||`${n}/login`),this.refreshUrl=this.baseUrl+(s||`${n}/refresh`),this.tenant=t,this.headers={"Content-Type":"application/json",...o},this.storage=a}async login(e,t,r=null,s={}){const o={identifier:e,password:t,...s};r&&(o.totp=r);const a=await fetch(this.loginUrl,{method:"POST",headers:this.headers,body:JSON.stringify(o)});if(!a.ok){const e=await a.text().catch(()=>a.statusText);throw new Error(`Login failed: ${a.status} ${e}`)}let n;try{n=await a.json()}catch(h){throw console.error("❌ JSON parse error:",h),new Error("Invalid JSON response from server")}const i=n.data||n;return this.storage&&(i.access_token&&(this.storage.accessToken=i.access_token),i.refresh_token&&(this.storage.refreshToken=i.refresh_token)),n}async refresh(e){if(r())return r();const t=(async()=>{const t=await fetch(this.refreshUrl,{method:"POST",headers:this.headers,body:JSON.stringify({refresh_token:e})});if(!t.ok){const e=await t.text().catch(()=>t.statusText);throw new Error(`Refresh failed: ${t.status} ${e}`)}let r;try{r=await t.json()}catch(o){throw console.error("❌ JSON parse error:",o),new Error("Invalid JSON response from server")}const s=r.data||r;return this.storage&&(s.access_token&&(this.storage.accessToken=s.access_token),s.refresh_token&&(this.storage.refreshToken=s.refresh_token)),r})();s(t);try{return await t}finally{s(null)}}}class a{constructor(e="auth",t=null){this.prefix=e,this.tenant=t}_key(e){return this.tenant?`${this.prefix}:${this.tenant}_${e}`:`${this.prefix}_${e}`}get accessToken(){return localStorage.getItem(this._key("access_token"))}set accessToken(e){null==e?localStorage.removeItem(this._key("access_token")):localStorage.setItem(this._key("access_token"),e)}get refreshToken(){return localStorage.getItem(this._key("refresh_token"))}set refreshToken(e){null==e?localStorage.removeItem(this._key("refresh_token")):localStorage.setItem(this._key("refresh_token"),e)}clear(){localStorage.removeItem(this._key("access_token")),localStorage.removeItem(this._key("refresh_token"))}}class n{constructor(e,t,r=null,s="introspect"){if(!t)throw new Error("introspectBaseUrl is required");this.client=e,this.introspectUrl=`${t}/${s.replace(/^\//,"")}`,this.storage=r||new a("auth",e.tenant||null)}_setAuthCookie(){const e=["auth_state=1","Path=/","Max-Age=300","SameSite=Lax"];"https:"===location.protocol&&e.push("Secure"),document.cookie=e.join("; ")}_clearAuthCookie(){document.cookie="auth_state=; Path=/; Max-Age=0"}async fetch(e,t={},r=null){const s=this.storage.accessToken,o=new Headers(t.headers||{});return s&&o.set("Authorization",`Bearer ${s}`),t.method&&"GET"!==t.method&&t.body?await this._xhrRequest(e,t,o,r):await this._fetchWithDownloadProgress(e,t,o,r)}async _fetchWithDownloadProgress(e,t,r,s){let o=await fetch(e,{...t,headers:r});if(401===o.status&&this.storage.refreshToken)try{const s=await this.client.refresh(this.storage.refreshToken);s.access_token&&(this.storage.accessToken=s.access_token),s.refresh_token&&(this.storage.refreshToken=s.refresh_token),r.set("Authorization",`Bearer ${this.storage.accessToken}`),o=await fetch(e,{...t,headers:r})}catch{throw this.storage.clear(),new Error("Unauthorized, please login again")}if(!s||!o.body)return o;const a=o.body.getReader(),n=+o.headers.get("Content-Length")||0;let i=0;const h=[];for(;;){const{done:e,value:t}=await a.read();if(e)break;if(h.push(t),i+=t.length,n){s(Math.round(i/n*100),i,n)}else s(null,i,null)}const c=new Blob(h);return new Response(c,o)}_xhrRequest(e,t,r,s){return new Promise((o,a)=>{const n=new XMLHttpRequest;n.open(t.method||"POST",e,!0);for(const[e,t]of r.entries())n.setRequestHeader(e,t);n.upload&&s&&(n.upload.onprogress=e=>{if(e.lengthComputable){const t=Math.round(e.loaded/e.total*100);s(t,e.loaded,e.total)}else s(null,e.loaded,null)}),n.onload=()=>{o(new Response(n.response,{status:n.status}))},n.onerror=()=>a(new Error("Network error")),n.send(t.body)})}async introspect(e=null){let t,r,s=e;if(this.storage&&!s&&(s=this.storage.accessToken),!s)throw this._clearAuthCookie(),new Error("No access token available for introspection");if(!this.introspectUrl)throw new Error("No introspect url config");try{t=await this.fetch(this.introspectUrl,{method:"GET"})}catch(o){throw o}if(401===t.status||403===t.status)throw this._clearAuthCookie(),new Error(`Unauthorized: ${t.status}`);if(t.status>=500)throw new Error(`Auth service error: ${t.status}`);if(!t.ok){const e=await t.text().catch(()=>t.statusText);throw new Error(`Introspect failed: ${t.status} ${e}`)}try{r=await t.json()}catch(a){throw new Error("Invalid JSON response from auth service")}if(!1===r.active)throw this._clearAuthCookie(),new Error("Token inactive");return this._setAuthCookie(),r}}const i={AuthClient:o,AuthFetch:n,TokenStorage:a};e.AuthClient=o,e.AuthFetch=n,e.TokenStorage=a,e.default=i,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
|
package/dist/index.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ export class AuthClient {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
export class AuthFetch {
|
|
82
|
-
constructor(authClient: AuthClient, introspectBaseUrl: string, storage?: TokenStorage, introspectPath?: string
|
|
82
|
+
constructor(authClient: AuthClient, introspectBaseUrl: string, storage?: TokenStorage, introspectPath?: string);
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* fetch wrapper — hỗ trợ:
|
package/package.json
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
1
|
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"import": "./dist/auth-sdk.es.js",
|
|
13
|
-
"require": "./dist/auth-sdk.umd.js"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "vite build && cp src/index.d.ts dist/index.d.ts && terser dist/auth-sdk.umd.js -o dist/auth-sdk.min.js --compress --mangle"
|
|
18
|
-
},
|
|
19
|
-
"devDependencies": {
|
|
20
|
-
"vite": "^5.0.0",
|
|
21
|
-
"terser": "^5.20.0"
|
|
2
|
+
"description": "VanillaJS Auth SDK for GH Platform",
|
|
3
|
+
"devDependencies": {
|
|
4
|
+
"terser": "^5.20.0",
|
|
5
|
+
"vite": "^5.0.0"
|
|
6
|
+
},
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/auth-sdk.es.js",
|
|
10
|
+
"require": "./dist/auth-sdk.umd.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
22
12
|
}
|
|
13
|
+
},
|
|
14
|
+
"main": "dist/auth-sdk.umd.js",
|
|
15
|
+
"module": "dist/auth-sdk.es.js",
|
|
16
|
+
"name": "@gh-platform/auth-sdk",
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "vite build && cp src/index.d.ts dist/index.d.ts && terser dist/auth-sdk.umd.js -o dist/auth-sdk.min.js --compress --mangle"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"types": "dist/index.d.ts",
|
|
22
|
+
"version": "1.0.12"
|
|
23
23
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ export class AuthClient {
|
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
export class AuthFetch {
|
|
82
|
-
constructor(authClient: AuthClient, introspectBaseUrl: string, storage?: TokenStorage, introspectPath?: string
|
|
82
|
+
constructor(authClient: AuthClient, introspectBaseUrl: string, storage?: TokenStorage, introspectPath?: string);
|
|
83
83
|
|
|
84
84
|
/**
|
|
85
85
|
* fetch wrapper — hỗ trợ:
|
package/src/middleware.js
CHANGED
|
@@ -10,6 +10,28 @@ export class AuthFetch {
|
|
|
10
10
|
this.introspectUrl = (`${introspectBaseUrl}/${introspectPath.replace(/^\//, "")}`);
|
|
11
11
|
this.storage = storage || new TokenStorage("auth", authClient.tenant || null);
|
|
12
12
|
}
|
|
13
|
+
|
|
14
|
+
_setAuthCookie() {
|
|
15
|
+
const maxAge = 60 * 5; // 5 phút
|
|
16
|
+
|
|
17
|
+
const parts = [
|
|
18
|
+
`auth_state=1`,
|
|
19
|
+
`Path=/`,
|
|
20
|
+
`Max-Age=${maxAge}`,
|
|
21
|
+
`SameSite=Lax`,
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
if (location.protocol === "https:") {
|
|
25
|
+
parts.push("Secure");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
document.cookie = parts.join("; ");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_clearAuthCookie() {
|
|
33
|
+
document.cookie = `auth_state=; Path=/; Max-Age=0`;
|
|
34
|
+
}
|
|
13
35
|
|
|
14
36
|
/**
|
|
15
37
|
* fetchWithProgress:
|
|
@@ -119,18 +141,44 @@ export class AuthFetch {
|
|
|
119
141
|
finalToken = this.storage.accessToken;
|
|
120
142
|
}
|
|
121
143
|
|
|
144
|
+
// ❌ không có token => chắc chắn chưa login
|
|
122
145
|
if (!finalToken) {
|
|
146
|
+
this._clearAuthCookie();
|
|
123
147
|
throw new Error("No access token available for introspection");
|
|
124
148
|
}
|
|
149
|
+
|
|
125
150
|
if (!this.introspectUrl) {
|
|
126
151
|
throw new Error("No introspect url config");
|
|
127
152
|
}
|
|
128
153
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
154
|
+
let res;
|
|
155
|
+
try {
|
|
156
|
+
res = await this.fetch(this.introspectUrl, {
|
|
157
|
+
method: "GET",
|
|
158
|
+
});
|
|
159
|
+
} catch (err) {
|
|
160
|
+
// ⚠️ NETWORK / FETCH ERROR
|
|
161
|
+
// KHÔNG clear cookie
|
|
162
|
+
throw err;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ===============================
|
|
166
|
+
// AUTH INVALID → CLEAR COOKIE
|
|
167
|
+
// ===============================
|
|
168
|
+
if (res.status === 401 || res.status === 403) {
|
|
169
|
+
this._clearAuthCookie();
|
|
170
|
+
throw new Error(`Unauthorized: ${res.status}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ===============================
|
|
174
|
+
// SERVER ERROR → KEEP COOKIE
|
|
175
|
+
// ===============================
|
|
176
|
+
if (res.status >= 500) {
|
|
177
|
+
throw new Error(`Auth service error: ${res.status}`);
|
|
178
|
+
}
|
|
132
179
|
|
|
133
180
|
if (!res.ok) {
|
|
181
|
+
// các status khác (400, 404...) → không clear
|
|
134
182
|
const text = await res.text().catch(() => res.statusText);
|
|
135
183
|
throw new Error(`Introspect failed: ${res.status} ${text}`);
|
|
136
184
|
}
|
|
@@ -139,10 +187,23 @@ export class AuthFetch {
|
|
|
139
187
|
try {
|
|
140
188
|
json = await res.json();
|
|
141
189
|
} catch (e) {
|
|
142
|
-
|
|
143
|
-
throw new Error("Invalid JSON response from
|
|
190
|
+
// parse lỗi => system error
|
|
191
|
+
throw new Error("Invalid JSON response from auth service");
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ===============================
|
|
195
|
+
// AUTH SERVICE NÓI TOKEN INVALID
|
|
196
|
+
// ===============================
|
|
197
|
+
if (json.active === false) {
|
|
198
|
+
this._clearAuthCookie();
|
|
199
|
+
throw new Error("Token inactive");
|
|
144
200
|
}
|
|
145
201
|
|
|
202
|
+
// ===============================
|
|
203
|
+
// AUTH OK → SET COOKIE
|
|
204
|
+
// ===============================
|
|
205
|
+
this._setAuthCookie();
|
|
206
|
+
|
|
146
207
|
return json;
|
|
147
208
|
}
|
|
148
209
|
}
|