@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 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)
@@ -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
- const res = await this.fetch(this.introspectUrl, {
237
- method: "GET"
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
- console.error(" JSON parse error:", e);
248
- throw new Error("Invalid JSON response from server");
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
  }
@@ -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:n=null}){if(!e)throw new Error("baseUrl is required");this.baseUrl=e.replace(/\/$/,"");const a=t?`/api/v1/${t}/auth`:"/api/v1/auth";this.loginUrl=this.baseUrl+(r||`${a}/login`),this.refreshUrl=this.baseUrl+(s||`${a}/refresh`),this.tenant=t,this.headers={"Content-Type":"application/json",...o},this.storage=n}async login(e,t,r=null,s={}){const o={identifier:e,password:t,...s};r&&(o.totp=r);const n=await fetch(this.loginUrl,{method:"POST",headers:this.headers,body:JSON.stringify(o)});if(!n.ok){const e=await n.text().catch(()=>n.statusText);throw new Error(`Login failed: ${n.status} ${e}`)}let a;try{a=await n.json()}catch(e){throw console.error("❌ JSON parse error:",e),new Error("Invalid JSON response from server")}const i=a.data||a;return this.storage&&(i.access_token&&(this.storage.accessToken=i.access_token),i.refresh_token&&(this.storage.refreshToken=i.refresh_token)),a}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 n{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 a{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 n("auth",e.tenant||null)}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 n=o.body.getReader(),a=+o.headers.get("Content-Length")||0;let i=0;const h=[];for(;;){const{done:e,value:t}=await n.read();if(e)break;h.push(t),i+=t.length,a?s(Math.round(i/a*100),i,a):s(null,i,null)}const c=new Blob(h);return new Response(c,o)}_xhrRequest(e,t,r,s){return new Promise((o,n)=>{const a=new XMLHttpRequest;a.open(t.method||"POST",e,!0);for(const[e,t]of r.entries())a.setRequestHeader(e,t);a.upload&&s&&(a.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)}),a.onload=()=>{o(new Response(a.response,{status:a.status}))},a.onerror=()=>n(new Error("Network error")),a.send(t.body)})}async introspect(e=null){let t=e;if(this.storage&&!t&&(t=this.storage.accessToken),!t)throw new Error("No access token available for introspection");if(!this.introspectUrl)throw new Error("No introspect url config");const r=await this.fetch(this.introspectUrl,{method:"GET"});if(!r.ok){const e=await r.text().catch(()=>r.statusText);throw new Error(`Introspect failed: ${r.status} ${e}`)}let s;try{s=await r.json()}catch(e){throw console.error(" JSON parse error:",e),new Error("Invalid JSON response from server")}return s}}const i={AuthClient:o,AuthFetch:a,TokenStorage:n};e.AuthClient=o,e.AuthFetch=a,e.TokenStorage=n,e.default=i,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
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"}})});
@@ -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:n=null}){if(!e)throw new Error("baseUrl is required");this.baseUrl=e.replace(/\/$/,"");const a=t?`/api/v1/${t}/auth`:"/api/v1/auth";this.loginUrl=this.baseUrl+(r||`${a}/login`),this.refreshUrl=this.baseUrl+(s||`${a}/refresh`),this.tenant=t,this.headers={"Content-Type":"application/json",...o},this.storage=n}async login(e,t,r=null,s={}){const o={identifier:e,password:t,...s};r&&(o.totp=r);const n=await fetch(this.loginUrl,{method:"POST",headers:this.headers,body:JSON.stringify(o)});if(!n.ok){const e=await n.text().catch(()=>n.statusText);throw new Error(`Login failed: ${n.status} ${e}`)}let a;try{a=await n.json()}catch(h){throw console.error("❌ JSON parse error:",h),new Error("Invalid JSON response from server")}const i=a.data||a;return this.storage&&(i.access_token&&(this.storage.accessToken=i.access_token),i.refresh_token&&(this.storage.refreshToken=i.refresh_token)),a}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 n{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 a{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 n("auth",e.tenant||null)}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 n=o.body.getReader(),a=+o.headers.get("Content-Length")||0;let i=0;const h=[];for(;;){const{done:e,value:t}=await n.read();if(e)break;if(h.push(t),i+=t.length,a){s(Math.round(i/a*100),i,a)}else s(null,i,null)}const c=new Blob(h);return new Response(c,o)}_xhrRequest(e,t,r,s){return new Promise((o,n)=>{const a=new XMLHttpRequest;a.open(t.method||"POST",e,!0);for(const[e,t]of r.entries())a.setRequestHeader(e,t);a.upload&&s&&(a.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)}),a.onload=()=>{o(new Response(a.response,{status:a.status}))},a.onerror=()=>n(new Error("Network error")),a.send(t.body)})}async introspect(e=null){let t=e;if(this.storage&&!t&&(t=this.storage.accessToken),!t)throw new Error("No access token available for introspection");if(!this.introspectUrl)throw new Error("No introspect url config");const r=await this.fetch(this.introspectUrl,{method:"GET"});if(!r.ok){const e=await r.text().catch(()=>r.statusText);throw new Error(`Introspect failed: ${r.status} ${e}`)}let s;try{s=await r.json()}catch(o){throw console.error(" JSON parse error:",o),new Error("Invalid JSON response from server")}return s}}const i={AuthClient:o,AuthFetch:a,TokenStorage:n};e.AuthClient=o,e.AuthFetch=a,e.TokenStorage=n,e.default=i,Object.defineProperties(e,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})});
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 = "introspect");
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
- "name": "@gh-platform/auth-sdk",
3
- "version": "1.0.10",
4
- "description": "VanillaJS Auth SDK for GH Platform",
5
- "type": "module",
6
- "main": "dist/auth-sdk.umd.js",
7
- "module": "dist/auth-sdk.es.js",
8
- "types": "dist/index.d.ts",
9
- "exports": {
10
- ".": {
11
- "types": "./dist/index.d.ts",
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 = "introspect");
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
- const res = await this.fetch(this.introspectUrl, {
130
- method: "GET",
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
- console.error("❌ JSON parse error:", e);
143
- throw new Error("Invalid JSON response from server");
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
  }