@hengkianggia/sso-erlangga 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 ADDED
@@ -0,0 +1,119 @@
1
+ # package-sso
2
+
3
+ Modul Helper SSO (Single Sign-On) yang menyediakan fungsionalitas untuk konfigurasi login, parsing token, dan pertukaran kode otorisasi berbasis PKCE.
4
+
5
+ ## Instalasi
6
+
7
+ ```bash
8
+ npm install package-sso
9
+ # atau
10
+ pnpm install package-sso
11
+ # atau
12
+ yarn add package-sso
13
+ ```
14
+
15
+ ## Penggunaan
16
+
17
+ Dokumentasi di bawah ini merupakan referensi fungsi-fungsi dari `sso-helper`.
18
+
19
+ ### 1. `generateSSOLoginUrl(config: SSOConfig)`
20
+ Fungsi ini digunakan untuk menghasilkan URL halaman login SSO. Fungsi ini juga secara otomatis meng-generate `code_challenge` untuk standar PKCE dan mengamankan `code_verifier` di dalam `localStorage`.
21
+
22
+ **Tipe Data (Interface):**
23
+ ```typescript
24
+ interface SSOConfig {
25
+ clientId: string; // Client ID aplikasi Anda
26
+ redirectUri: string; // Callback URI
27
+ ssoBaseUrl: string; // URL dasar / Base URL SSO
28
+ }
29
+ ```
30
+
31
+ **Contoh Penggunaan:**
32
+ ```typescript
33
+ import { generateSSOLoginUrl } from 'package-sso';
34
+
35
+ async function loginStart() {
36
+ const loginUrl = await generateSSOLoginUrl({
37
+ clientId: 'app-client-123',
38
+ redirectUri: 'http://localhost:3000/callback',
39
+ ssoBaseUrl: 'https://sso.example.com'
40
+ });
41
+
42
+ // Arahkan user ke halaman login SSO:
43
+ window.location.href = loginUrl;
44
+ }
45
+ ```
46
+
47
+ ---
48
+
49
+ ### 2. `getSSOTokenPayload(token: string)`
50
+ Fungsi ini bertugas untuk mendekode dan melakukan parsing payload dari sebuah string token berformat JWT.
51
+
52
+ **Contoh Penggunaan:**
53
+ ```typescript
54
+ import { getSSOTokenPayload } from 'package-sso';
55
+
56
+ const tokenUrl = "eyJhbGc.eyJzdWIiOiIxMjMifQ.sgn";
57
+ const payload = getSSOTokenPayload(tokenUrl);
58
+
59
+ if (payload) {
60
+ console.log("Data Payload User:", payload);
61
+ } else {
62
+ console.log("Token tidak valid / gagal parsing");
63
+ }
64
+ ```
65
+
66
+ ---
67
+
68
+ ### 3. `getSSOExchangeBody(config: SSOExchangeConfig)`
69
+ Fungsi ini sangat penting ketika user dikembalikan dari SSO ke aplikasi Anda (Callback). Ia akan menyiapkan Object body untuk ditukar (_exchange_) dengan Access Token. Secara otomatis mengambil `code_verifier` yang dibentuk pada tahap pertama dari `localStorage`.
70
+
71
+ **Tipe Data (Interface):**
72
+ ```typescript
73
+ interface SSOExchangeConfig {
74
+ code: string; // Kode otorisasi yg didapat dari query parameter URL Callback
75
+ clientId: string; // Client ID aplikasi
76
+ redirectUri: string; // URI Redirect yang dikonfigurasi
77
+ }
78
+ ```
79
+
80
+ **Contoh Penggunaan:**
81
+ ```typescript
82
+ import { getSSOExchangeBody } from 'package-sso';
83
+
84
+ // Misalnya didapatkan dari URL query `?code=xyZ123`
85
+ const authCode = "xyZ123";
86
+
87
+ try {
88
+ const requestBody = getSSOExchangeBody({
89
+ code: authCode,
90
+ clientId: 'app-client-123',
91
+ redirectUri: 'http://localhost:3000/callback'
92
+ });
93
+
94
+ // Anda dapat menggunakan `requestBody` ini untuk Post payload Axios/Fetch
95
+ // ke Endpoin Token SSO backend Anda.
96
+ /*
97
+ axios.post('https://sso.example.com/token', requestBody)
98
+ */
99
+
100
+ } catch (error) {
101
+ // Menangkap error jika `code_verifier` tidak ditemukan di browser
102
+ console.error(error.message);
103
+ }
104
+ ```
105
+
106
+ ---
107
+
108
+ ### 4. `clearSSOData()`
109
+ Membersihkan seluruh jejak local state terkait SSO (`sso_state`, `sso_code_verifier`, dan `sso_token`) di dalam `localStorage`.
110
+
111
+ **Contoh Penggunaan:**
112
+ ```typescript
113
+ import { clearSSOData } from 'package-sso';
114
+
115
+ function doLogout() {
116
+ clearSSOData();
117
+ // Lanjut redirect ke home, dsb...
118
+ }
119
+ ```
@@ -0,0 +1,25 @@
1
+ interface SSOConfig {
2
+ clientId: string;
3
+ redirectUri: string;
4
+ ssoBaseUrl: string;
5
+ }
6
+ declare function generateSSOLoginUrl(config: SSOConfig): Promise<string>;
7
+ declare function getSSOTokenPayload(token: string): Record<string, any> | null;
8
+ interface SSOExchangeConfig {
9
+ code: string;
10
+ clientId: string;
11
+ redirectUri: string;
12
+ }
13
+ declare function getSSOExchangeBody(config: SSOExchangeConfig): {
14
+ grant_type: string;
15
+ code: string;
16
+ redirect_uri: string;
17
+ client_id: string;
18
+ code_verifier: string;
19
+ };
20
+ declare function clearSSOData(): void;
21
+
22
+ declare function generateRandomString(length: number): string;
23
+ declare function generateCodeChallenge2(codeVerifier: string): Promise<string>;
24
+
25
+ export { type SSOConfig, type SSOExchangeConfig, clearSSOData, generateCodeChallenge2, generateRandomString, generateSSOLoginUrl, getSSOExchangeBody, getSSOTokenPayload };
@@ -0,0 +1,25 @@
1
+ interface SSOConfig {
2
+ clientId: string;
3
+ redirectUri: string;
4
+ ssoBaseUrl: string;
5
+ }
6
+ declare function generateSSOLoginUrl(config: SSOConfig): Promise<string>;
7
+ declare function getSSOTokenPayload(token: string): Record<string, any> | null;
8
+ interface SSOExchangeConfig {
9
+ code: string;
10
+ clientId: string;
11
+ redirectUri: string;
12
+ }
13
+ declare function getSSOExchangeBody(config: SSOExchangeConfig): {
14
+ grant_type: string;
15
+ code: string;
16
+ redirect_uri: string;
17
+ client_id: string;
18
+ code_verifier: string;
19
+ };
20
+ declare function clearSSOData(): void;
21
+
22
+ declare function generateRandomString(length: number): string;
23
+ declare function generateCodeChallenge2(codeVerifier: string): Promise<string>;
24
+
25
+ export { type SSOConfig, type SSOExchangeConfig, clearSSOData, generateCodeChallenge2, generateRandomString, generateSSOLoginUrl, getSSOExchangeBody, getSSOTokenPayload };
package/dist/index.js ADDED
@@ -0,0 +1,114 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ clearSSOData: () => clearSSOData,
24
+ generateCodeChallenge2: () => generateCodeChallenge2,
25
+ generateRandomString: () => generateRandomString,
26
+ generateSSOLoginUrl: () => generateSSOLoginUrl,
27
+ getSSOExchangeBody: () => getSSOExchangeBody,
28
+ getSSOTokenPayload: () => getSSOTokenPayload
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+
32
+ // src/helper.ts
33
+ function base64UrlEncode(arrayBuffer) {
34
+ const bytes = new Uint8Array(arrayBuffer);
35
+ let binary = "";
36
+ for (let i = 0; i < bytes.byteLength; i++) {
37
+ binary += String.fromCharCode(bytes[i]);
38
+ }
39
+ const base64 = btoa(binary);
40
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
41
+ }
42
+ function generateRandomString(length) {
43
+ const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
44
+ let result = "";
45
+ const values = new Uint32Array(length);
46
+ window.crypto.getRandomValues(values);
47
+ for (let i = 0; i < length; i++) {
48
+ result += charset[values[i] % charset.length];
49
+ }
50
+ return result;
51
+ }
52
+ async function generateCodeChallenge2(codeVerifier) {
53
+ const encoder = new TextEncoder();
54
+ const data = encoder.encode(codeVerifier);
55
+ const digest = await window.crypto.subtle.digest("SHA-256", data);
56
+ return base64UrlEncode(digest);
57
+ }
58
+
59
+ // src/sso-helper.ts
60
+ async function generateSSOLoginUrl(config) {
61
+ const codeVerifier = generateRandomString(64);
62
+ localStorage.setItem("sso_code_verifier", codeVerifier);
63
+ const codeChallenge = await generateCodeChallenge2(codeVerifier);
64
+ const params = new URLSearchParams({
65
+ response_type: "code",
66
+ client_id: config.clientId,
67
+ redirect_uri: config.redirectUri,
68
+ code_challenge: codeChallenge,
69
+ code_challenge_method: "S256"
70
+ });
71
+ return `${config.ssoBaseUrl}/callback?${params.toString()}`;
72
+ }
73
+ function getSSOTokenPayload(token) {
74
+ try {
75
+ const base64Url = token.split(".")[1];
76
+ if (!base64Url) return null;
77
+ const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
78
+ const jsonPayload = decodeURIComponent(
79
+ atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
80
+ );
81
+ return JSON.parse(jsonPayload);
82
+ } catch (error) {
83
+ console.error("Failed to parse SSO token payload", error);
84
+ return null;
85
+ }
86
+ }
87
+ function getSSOExchangeBody(config) {
88
+ const codeVerifier = localStorage.getItem("sso_code_verifier") || "";
89
+ if (!codeVerifier) {
90
+ throw new Error("Code verifier not found");
91
+ }
92
+ return {
93
+ grant_type: "authorization_code",
94
+ code: config.code,
95
+ redirect_uri: config.redirectUri,
96
+ client_id: config.clientId,
97
+ code_verifier: codeVerifier
98
+ };
99
+ }
100
+ function clearSSOData() {
101
+ localStorage.removeItem("sso_state");
102
+ localStorage.removeItem("sso_code_verifier");
103
+ localStorage.removeItem("sso_token");
104
+ }
105
+ // Annotate the CommonJS export names for ESM import in node:
106
+ 0 && (module.exports = {
107
+ clearSSOData,
108
+ generateCodeChallenge2,
109
+ generateRandomString,
110
+ generateSSOLoginUrl,
111
+ getSSOExchangeBody,
112
+ getSSOTokenPayload
113
+ });
114
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/helper.ts","../src/sso-helper.ts"],"sourcesContent":["export * from './sso-helper';\nexport * from './helper';\n","// Base64-url encoding logic\nfunction base64UrlEncode(arrayBuffer: ArrayBuffer): string {\n const bytes = new Uint8Array(arrayBuffer);\n let binary = '';\n // Convert bytes to string (chunking can help for very large arrays, but for SHA-256 (32 bytes) it's fine)\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n\n return base64\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\nexport function generateRandomString(length: number): string {\n const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n let result = '';\n const values = new Uint32Array(length);\n window.crypto.getRandomValues(values);\n for (let i = 0; i < length; i++) {\n result += charset[values[i] % charset.length];\n }\n return result;\n}\n\nexport async function generateCodeChallenge2(codeVerifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(codeVerifier);\n // Hash the data using SHA-256\n const digest = await window.crypto.subtle.digest('SHA-256', data);\n // Base64-url encode the hash\n return base64UrlEncode(digest);\n}\n","import { generateRandomString, generateCodeChallenge2 } from './helper';\n\nexport interface SSOConfig {\n clientId: string;\n redirectUri: string;\n ssoBaseUrl: string;\n}\n\nexport async function generateSSOLoginUrl(config: SSOConfig): Promise<string> {\n const codeVerifier = generateRandomString(64);\n\n localStorage.setItem('sso_code_verifier', codeVerifier);\n\n const codeChallenge = await generateCodeChallenge2(codeVerifier);\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n });\n\n return `${config.ssoBaseUrl}/callback?${params.toString()}`;\n}\n\nexport function getSSOTokenPayload(token: string): Record<string, any> | null {\n try {\n const base64Url = token.split('.')[1];\n if (!base64Url) return null;\n const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n );\n return JSON.parse(jsonPayload);\n } catch (error) {\n console.error('Failed to parse SSO token payload', error);\n return null;\n }\n}\n\nexport interface SSOExchangeConfig {\n code: string;\n clientId: string;\n redirectUri: string;\n}\n\nexport function getSSOExchangeBody(config: SSOExchangeConfig) {\n const codeVerifier = localStorage.getItem('sso_code_verifier') || '';\n if (!codeVerifier) {\n throw new Error('Code verifier not found');\n }\n\n return {\n grant_type: 'authorization_code',\n code: config.code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n code_verifier: codeVerifier,\n };\n}\n\nexport function clearSSOData(): void {\n localStorage.removeItem('sso_state');\n localStorage.removeItem('sso_code_verifier');\n localStorage.removeItem('sso_token'); // Assuming the token might be stored this way, though up to the app\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCA,SAAS,gBAAgB,aAAkC;AACzD,QAAM,QAAQ,IAAI,WAAW,WAAW;AACxC,MAAI,SAAS;AAEb,WAAS,IAAI,GAAG,IAAI,MAAM,YAAY,KAAK;AACzC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,QAAM,SAAS,KAAK,MAAM;AAE1B,SAAO,OACJ,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAEO,SAAS,qBAAqB,QAAwB;AAC3D,QAAM,UAAU;AAChB,MAAI,SAAS;AACb,QAAM,SAAS,IAAI,YAAY,MAAM;AACrC,SAAO,OAAO,gBAAgB,MAAM;AACpC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,cAAU,QAAQ,OAAO,CAAC,IAAI,QAAQ,MAAM;AAAA,EAC9C;AACA,SAAO;AACT;AAEA,eAAsB,uBAAuB,cAAuC;AAClF,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,YAAY;AAExC,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,OAAO,WAAW,IAAI;AAEhE,SAAO,gBAAgB,MAAM;AAC/B;;;AC1BA,eAAsB,oBAAoB,QAAoC;AAC5E,QAAM,eAAe,qBAAqB,EAAE;AAE5C,eAAa,QAAQ,qBAAqB,YAAY;AAEtD,QAAM,gBAAgB,MAAM,uBAAuB,YAAY;AAE/D,QAAM,SAAS,IAAI,gBAAgB;AAAA,IACjC,eAAe;AAAA,IACf,WAAW,OAAO;AAAA,IAClB,cAAc,OAAO;AAAA,IACrB,gBAAgB;AAAA,IAChB,uBAAuB;AAAA,EACzB,CAAC;AAED,SAAO,GAAG,OAAO,UAAU,aAAa,OAAO,SAAS,CAAC;AAC3D;AAEO,SAAS,mBAAmB,OAA2C;AAC5E,MAAI;AACF,UAAM,YAAY,MAAM,MAAM,GAAG,EAAE,CAAC;AACpC,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,SAAS,UAAU,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC7D,UAAM,cAAc;AAAA,MAClB,KAAK,MAAM,EACR,MAAM,EAAE,EACR,IAAI,CAAC,MAAM,OAAO,OAAO,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAAC,EAChE,KAAK,EAAE;AAAA,IACZ;AACA,WAAO,KAAK,MAAM,WAAW;AAAA,EAC/B,SAAS,OAAO;AACd,YAAQ,MAAM,qCAAqC,KAAK;AACxD,WAAO;AAAA,EACT;AACF;AAQO,SAAS,mBAAmB,QAA2B;AAC5D,QAAM,eAAe,aAAa,QAAQ,mBAAmB,KAAK;AAClE,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,MAAM,OAAO;AAAA,IACb,cAAc,OAAO;AAAA,IACrB,WAAW,OAAO;AAAA,IAClB,eAAe;AAAA,EACjB;AACF;AAEO,SAAS,eAAqB;AACnC,eAAa,WAAW,WAAW;AACnC,eAAa,WAAW,mBAAmB;AAC3C,eAAa,WAAW,WAAW;AACrC;","names":[]}
package/dist/index.mjs ADDED
@@ -0,0 +1,82 @@
1
+ // src/helper.ts
2
+ function base64UrlEncode(arrayBuffer) {
3
+ const bytes = new Uint8Array(arrayBuffer);
4
+ let binary = "";
5
+ for (let i = 0; i < bytes.byteLength; i++) {
6
+ binary += String.fromCharCode(bytes[i]);
7
+ }
8
+ const base64 = btoa(binary);
9
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
10
+ }
11
+ function generateRandomString(length) {
12
+ const charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~";
13
+ let result = "";
14
+ const values = new Uint32Array(length);
15
+ window.crypto.getRandomValues(values);
16
+ for (let i = 0; i < length; i++) {
17
+ result += charset[values[i] % charset.length];
18
+ }
19
+ return result;
20
+ }
21
+ async function generateCodeChallenge2(codeVerifier) {
22
+ const encoder = new TextEncoder();
23
+ const data = encoder.encode(codeVerifier);
24
+ const digest = await window.crypto.subtle.digest("SHA-256", data);
25
+ return base64UrlEncode(digest);
26
+ }
27
+
28
+ // src/sso-helper.ts
29
+ async function generateSSOLoginUrl(config) {
30
+ const codeVerifier = generateRandomString(64);
31
+ localStorage.setItem("sso_code_verifier", codeVerifier);
32
+ const codeChallenge = await generateCodeChallenge2(codeVerifier);
33
+ const params = new URLSearchParams({
34
+ response_type: "code",
35
+ client_id: config.clientId,
36
+ redirect_uri: config.redirectUri,
37
+ code_challenge: codeChallenge,
38
+ code_challenge_method: "S256"
39
+ });
40
+ return `${config.ssoBaseUrl}/callback?${params.toString()}`;
41
+ }
42
+ function getSSOTokenPayload(token) {
43
+ try {
44
+ const base64Url = token.split(".")[1];
45
+ if (!base64Url) return null;
46
+ const base64 = base64Url.replace(/-/g, "+").replace(/_/g, "/");
47
+ const jsonPayload = decodeURIComponent(
48
+ atob(base64).split("").map((c) => "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2)).join("")
49
+ );
50
+ return JSON.parse(jsonPayload);
51
+ } catch (error) {
52
+ console.error("Failed to parse SSO token payload", error);
53
+ return null;
54
+ }
55
+ }
56
+ function getSSOExchangeBody(config) {
57
+ const codeVerifier = localStorage.getItem("sso_code_verifier") || "";
58
+ if (!codeVerifier) {
59
+ throw new Error("Code verifier not found");
60
+ }
61
+ return {
62
+ grant_type: "authorization_code",
63
+ code: config.code,
64
+ redirect_uri: config.redirectUri,
65
+ client_id: config.clientId,
66
+ code_verifier: codeVerifier
67
+ };
68
+ }
69
+ function clearSSOData() {
70
+ localStorage.removeItem("sso_state");
71
+ localStorage.removeItem("sso_code_verifier");
72
+ localStorage.removeItem("sso_token");
73
+ }
74
+ export {
75
+ clearSSOData,
76
+ generateCodeChallenge2,
77
+ generateRandomString,
78
+ generateSSOLoginUrl,
79
+ getSSOExchangeBody,
80
+ getSSOTokenPayload
81
+ };
82
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/helper.ts","../src/sso-helper.ts"],"sourcesContent":["// Base64-url encoding logic\nfunction base64UrlEncode(arrayBuffer: ArrayBuffer): string {\n const bytes = new Uint8Array(arrayBuffer);\n let binary = '';\n // Convert bytes to string (chunking can help for very large arrays, but for SHA-256 (32 bytes) it's fine)\n for (let i = 0; i < bytes.byteLength; i++) {\n binary += String.fromCharCode(bytes[i]);\n }\n const base64 = btoa(binary);\n\n return base64\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '');\n}\n\nexport function generateRandomString(length: number): string {\n const charset = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~';\n let result = '';\n const values = new Uint32Array(length);\n window.crypto.getRandomValues(values);\n for (let i = 0; i < length; i++) {\n result += charset[values[i] % charset.length];\n }\n return result;\n}\n\nexport async function generateCodeChallenge2(codeVerifier: string): Promise<string> {\n const encoder = new TextEncoder();\n const data = encoder.encode(codeVerifier);\n // Hash the data using SHA-256\n const digest = await window.crypto.subtle.digest('SHA-256', data);\n // Base64-url encode the hash\n return base64UrlEncode(digest);\n}\n","import { generateRandomString, generateCodeChallenge2 } from './helper';\n\nexport interface SSOConfig {\n clientId: string;\n redirectUri: string;\n ssoBaseUrl: string;\n}\n\nexport async function generateSSOLoginUrl(config: SSOConfig): Promise<string> {\n const codeVerifier = generateRandomString(64);\n\n localStorage.setItem('sso_code_verifier', codeVerifier);\n\n const codeChallenge = await generateCodeChallenge2(codeVerifier);\n\n const params = new URLSearchParams({\n response_type: 'code',\n client_id: config.clientId,\n redirect_uri: config.redirectUri,\n code_challenge: codeChallenge,\n code_challenge_method: 'S256',\n });\n\n return `${config.ssoBaseUrl}/callback?${params.toString()}`;\n}\n\nexport function getSSOTokenPayload(token: string): Record<string, any> | null {\n try {\n const base64Url = token.split('.')[1];\n if (!base64Url) return null;\n const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');\n const jsonPayload = decodeURIComponent(\n atob(base64)\n .split('')\n .map((c) => '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2))\n .join('')\n );\n return JSON.parse(jsonPayload);\n } catch (error) {\n console.error('Failed to parse SSO token payload', error);\n return null;\n }\n}\n\nexport interface SSOExchangeConfig {\n code: string;\n clientId: string;\n redirectUri: string;\n}\n\nexport function getSSOExchangeBody(config: SSOExchangeConfig) {\n const codeVerifier = localStorage.getItem('sso_code_verifier') || '';\n if (!codeVerifier) {\n throw new Error('Code verifier not found');\n }\n\n return {\n grant_type: 'authorization_code',\n code: config.code,\n redirect_uri: config.redirectUri,\n client_id: config.clientId,\n code_verifier: codeVerifier,\n };\n}\n\nexport function clearSSOData(): void {\n localStorage.removeItem('sso_state');\n localStorage.removeItem('sso_code_verifier');\n localStorage.removeItem('sso_token'); // Assuming the token might be stored this way, though up to the app\n}\n"],"mappings":";AACA,SAAS,gBAAgB,aAAkC;AACzD,QAAM,QAAQ,IAAI,WAAW,WAAW;AACxC,MAAI,SAAS;AAEb,WAAS,IAAI,GAAG,IAAI,MAAM,YAAY,KAAK;AACzC,cAAU,OAAO,aAAa,MAAM,CAAC,CAAC;AAAA,EACxC;AACA,QAAM,SAAS,KAAK,MAAM;AAE1B,SAAO,OACJ,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,GAAG,EAClB,QAAQ,OAAO,EAAE;AACtB;AAEO,SAAS,qBAAqB,QAAwB;AAC3D,QAAM,UAAU;AAChB,MAAI,SAAS;AACb,QAAM,SAAS,IAAI,YAAY,MAAM;AACrC,SAAO,OAAO,gBAAgB,MAAM;AACpC,WAAS,IAAI,GAAG,IAAI,QAAQ,KAAK;AAC/B,cAAU,QAAQ,OAAO,CAAC,IAAI,QAAQ,MAAM;AAAA,EAC9C;AACA,SAAO;AACT;AAEA,eAAsB,uBAAuB,cAAuC;AAClF,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,YAAY;AAExC,QAAM,SAAS,MAAM,OAAO,OAAO,OAAO,OAAO,WAAW,IAAI;AAEhE,SAAO,gBAAgB,MAAM;AAC/B;;;AC1BA,eAAsB,oBAAoB,QAAoC;AAC5E,QAAM,eAAe,qBAAqB,EAAE;AAE5C,eAAa,QAAQ,qBAAqB,YAAY;AAEtD,QAAM,gBAAgB,MAAM,uBAAuB,YAAY;AAE/D,QAAM,SAAS,IAAI,gBAAgB;AAAA,IACjC,eAAe;AAAA,IACf,WAAW,OAAO;AAAA,IAClB,cAAc,OAAO;AAAA,IACrB,gBAAgB;AAAA,IAChB,uBAAuB;AAAA,EACzB,CAAC;AAED,SAAO,GAAG,OAAO,UAAU,aAAa,OAAO,SAAS,CAAC;AAC3D;AAEO,SAAS,mBAAmB,OAA2C;AAC5E,MAAI;AACF,UAAM,YAAY,MAAM,MAAM,GAAG,EAAE,CAAC;AACpC,QAAI,CAAC,UAAW,QAAO;AACvB,UAAM,SAAS,UAAU,QAAQ,MAAM,GAAG,EAAE,QAAQ,MAAM,GAAG;AAC7D,UAAM,cAAc;AAAA,MAClB,KAAK,MAAM,EACR,MAAM,EAAE,EACR,IAAI,CAAC,MAAM,OAAO,OAAO,EAAE,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,MAAM,EAAE,CAAC,EAChE,KAAK,EAAE;AAAA,IACZ;AACA,WAAO,KAAK,MAAM,WAAW;AAAA,EAC/B,SAAS,OAAO;AACd,YAAQ,MAAM,qCAAqC,KAAK;AACxD,WAAO;AAAA,EACT;AACF;AAQO,SAAS,mBAAmB,QAA2B;AAC5D,QAAM,eAAe,aAAa,QAAQ,mBAAmB,KAAK;AAClE,MAAI,CAAC,cAAc;AACjB,UAAM,IAAI,MAAM,yBAAyB;AAAA,EAC3C;AAEA,SAAO;AAAA,IACL,YAAY;AAAA,IACZ,MAAM,OAAO;AAAA,IACb,cAAc,OAAO;AAAA,IACrB,WAAW,OAAO;AAAA,IAClB,eAAe;AAAA,EACjB;AACF;AAEO,SAAS,eAAqB;AACnC,eAAa,WAAW,WAAW;AACnC,eAAa,WAAW,mBAAmB;AAC3C,eAAa,WAAW,WAAW;AACrC;","names":[]}
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@hengkianggia/sso-erlangga",
3
+ "version": "1.0.0",
4
+ "description": "Helper package for SSO PKCE integration",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.mjs",
7
+ "types": "dist/index.d.ts",
8
+ "scripts": {
9
+ "build": "tsup",
10
+ "dev": "tsup --watch",
11
+ "lint": "eslint src/",
12
+ "typecheck": "tsc --noEmit",
13
+ "prepare": "pnpm run build"
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "keywords": [
19
+ "sso",
20
+ "pkce",
21
+ "oauth2",
22
+ "auth"
23
+ ],
24
+ "author": "",
25
+ "license": "ISC",
26
+ "devDependencies": {
27
+ "tsup": "^8.0.2",
28
+ "typescript": "^5.4.3"
29
+ }
30
+ }