nocal-auth-sdk 0.1.9 → 0.2.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/dist/index.d.ts CHANGED
@@ -1,32 +1,22 @@
1
- import { NocalClientConfig, OAuthLoginReq, User as NocalUser, Session as NocalSession } from "./types";
1
+ import { NocalClientConfig, OAuthLoginReq, NocalUser, NocalSession } from "./types";
2
2
  export declare class NocalAuthSDK {
3
- private supabase;
4
3
  private clientId;
5
4
  private redirectUri;
5
+ private authServerUrl;
6
+ private currentSession;
7
+ private currentUser;
6
8
  constructor(config: NocalClientConfig);
7
9
  /**
8
- * Mở Popup UI Đăng nhập/Đăng ký tập trung của Nocal
9
- */
10
- signInWithNocal(req?: OAuthLoginReq): Promise<NocalUser>;
11
- /**
12
- * Lấy thông tin User hiện tại
10
+ * Mở Popup UI Đăng nhập & Ủy quyền OAuth 2.0 với PKCE
13
11
  */
12
+ signInWithNocal(req?: OAuthLoginReq): Promise<{
13
+ user: NocalUser;
14
+ session: NocalSession;
15
+ }>;
16
+ private exchangeCodeForToken;
17
+ private fetchUserInfo;
14
18
  getUser(): Promise<NocalUser | null>;
15
- /**
16
- * Lấy Session / Access Token hiện tại
17
- */
18
19
  getSession(): Promise<NocalSession | null>;
19
- /**
20
- * Đăng xuất khỏi hệ thống
21
- */
22
20
  signOut(): Promise<void>;
23
- /**
24
- * Lắng nghe sự kiện đăng nhập / đăng xuất
25
- */
26
- onAuthStateChange(callback: (event: string, session: NocalSession | null) => void): {
27
- data: {
28
- subscription: import("@supabase/auth-js").Subscription;
29
- };
30
- };
31
21
  }
32
22
  export * from "./types";
package/dist/index.js CHANGED
@@ -1,109 +1,171 @@
1
- import { createClient, } from "@supabase/supabase-js";
2
- const NOCAL_AUTH_SERVER_URL = "https://hldymiruucsqqzicnwdv.supabase.co";
3
- const NOCAL_ANON_KEY = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6ImhsZHltaXJ1dWNzcXF6aWNud2R2Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3ODk2MTQ2NzAsImV4cCI6MjEwNTE5MDY3MH0.dLaPWdgTV8mRVToNT1XLg1EUjHvyvRRK2qc0DQAu8YI";
1
+ const DEFAULT_AUTH_SERVER_URL = "http://localhost:3001";
2
+ // Helper for PKCE crypto
3
+ function base64URLEncode(buffer) {
4
+ let binary = "";
5
+ const bytes = new Uint8Array(buffer);
6
+ for (let i = 0; i < bytes.byteLength; i++) {
7
+ binary += String.fromCharCode(bytes[i]);
8
+ }
9
+ return btoa(binary)
10
+ .replace(/\+/g, "-")
11
+ .replace(/\//g, "_")
12
+ .replace(/=+$/, "");
13
+ }
14
+ async function generateCodeChallenge(verifier) {
15
+ const encoder = new TextEncoder();
16
+ const data = encoder.encode(verifier);
17
+ const digest = await crypto.subtle.digest("SHA-256", data);
18
+ return base64URLEncode(new Uint8Array(digest));
19
+ }
20
+ function generateRandomString(length = 32) {
21
+ const array = new Uint8Array(length);
22
+ crypto.getRandomValues(array);
23
+ return Array.from(array, (byte) => byte.toString(16).padStart(2, "0")).join("");
24
+ }
4
25
  export class NocalAuthSDK {
5
26
  constructor(config) {
27
+ this.currentSession = null;
28
+ this.currentUser = null;
6
29
  this.clientId = config.clientId;
7
30
  this.redirectUri = config.redirectUri;
8
- this.supabase = createClient(NOCAL_AUTH_SERVER_URL, NOCAL_ANON_KEY, {
9
- auth: {
10
- autoRefreshToken: true,
11
- persistSession: true,
12
- detectSessionInUrl: true,
13
- },
14
- });
31
+ this.authServerUrl = config.authServerUrl || DEFAULT_AUTH_SERVER_URL;
32
+ // Load session from localStorage if available
33
+ if (typeof window !== "undefined") {
34
+ try {
35
+ const stored = localStorage.getItem("nocal_auth_session");
36
+ if (stored) {
37
+ const parsed = JSON.parse(stored);
38
+ this.currentSession = parsed.session;
39
+ this.currentUser = parsed.user;
40
+ }
41
+ }
42
+ catch (e) {
43
+ // ignore
44
+ }
45
+ }
15
46
  }
16
47
  /**
17
- * Mở Popup UI Đăng nhập/Đăng ký tập trung của Nocal
48
+ * Mở Popup UI Đăng nhập & Ủy quyền OAuth 2.0 với PKCE
18
49
  */
19
50
  async signInWithNocal(req) {
20
- const targetRedirect = req?.redirectTo || this.redirectUri;
21
- const provider = (req?.provider || "google");
22
- // 1. Gọi Supabase SDK để sinh URL ủy quyền chuẩn OAuth 2.0 / PKCE
23
- const { data, error } = await this.supabase.auth.signInWithOAuth({
24
- provider,
25
- options: {
26
- redirectTo: targetRedirect,
27
- skipBrowserRedirect: true, // Lấy URL trả về, không nhảy trang tab chính
28
- queryParams: {
29
- client_id: this.clientId, // Gắn Client ID để Auth Server nhận diện App
30
- },
31
- },
32
- });
33
- if (error)
34
- throw error;
35
- if (!data?.url)
36
- throw new Error("CANNOT_GENERATE_AUTH_URL");
37
- // 2. Tính toán kích thước và căn giữa màn hình Popup
51
+ const state = req?.state || generateRandomString(16);
52
+ const codeVerifier = generateRandomString(32);
53
+ const codeChallenge = await generateCodeChallenge(codeVerifier);
54
+ const authUrl = new URL(`${this.authServerUrl}/oauth/authorize`);
55
+ authUrl.searchParams.set("client_id", this.clientId);
56
+ authUrl.searchParams.set("redirect_uri", this.redirectUri);
57
+ authUrl.searchParams.set("response_type", "code");
58
+ authUrl.searchParams.set("state", state);
59
+ authUrl.searchParams.set("code_challenge", codeChallenge);
60
+ authUrl.searchParams.set("code_challenge_method", "S256");
38
61
  const width = 480;
39
62
  const height = 680;
40
- const left = typeof window !== "undefined"
41
- ? window.screenX + (window.outerWidth - width) / 2
42
- : 0;
43
- const top = typeof window !== "undefined"
44
- ? window.screenY + (window.outerHeight - height) / 2
45
- : 0;
46
- // 3. Mở Cửa sổ Popup chứa UI đăng nhập Nocal
47
- const popup = window.open(data.url, "NocalAuthPopup", `width=${width},height=${height},left=${left},top=${top},status=yes,scrollbars=yes`);
63
+ const left = typeof window !== "undefined" ? window.screenX + (window.outerWidth - width) / 2 : 0;
64
+ const top = typeof window !== "undefined" ? window.screenY + (window.outerHeight - height) / 2 : 0;
65
+ const popup = window.open(authUrl.toString(), "NocalAuthPopup", `width=${width},height=${height},left=${left},top=${top},status=yes,scrollbars=yes`);
48
66
  if (!popup) {
49
67
  throw new Error("POPUP_BLOCKED");
50
68
  }
51
- // 4. Lắng nghe và kiểm tra khi Popup đóng
52
69
  return new Promise((resolve, reject) => {
53
- const isPopupClosed = () => {
70
+ const allowedOrigin = new URL(this.authServerUrl).origin;
71
+ const messageListener = async (event) => {
72
+ if (event.origin !== allowedOrigin)
73
+ return;
74
+ const data = event.data;
75
+ if (!data || (data.type !== "NOCAL_OAUTH_CODE" && data.type !== "NOCAL_OAUTH_ERROR")) {
76
+ return;
77
+ }
78
+ window.removeEventListener("message", messageListener);
79
+ clearInterval(checkClosedTimer);
80
+ if (data.type === "NOCAL_OAUTH_ERROR" || data.error) {
81
+ popup.close();
82
+ reject(new Error(data.error || "OAUTH_ERROR"));
83
+ return;
84
+ }
85
+ if (data.state && data.state !== state) {
86
+ popup.close();
87
+ reject(new Error("OAUTH_STATE_MISMATCH"));
88
+ return;
89
+ }
90
+ const code = data.code;
91
+ if (!code) {
92
+ popup.close();
93
+ reject(new Error("MISSING_AUTHORIZATION_CODE"));
94
+ return;
95
+ }
54
96
  try {
55
- return popup.closed;
97
+ popup.close();
98
+ // Đổi code lấy token
99
+ const session = await this.exchangeCodeForToken(code, codeVerifier);
100
+ const user = await this.fetchUserInfo(session.access_token);
101
+ this.currentSession = session;
102
+ this.currentUser = user;
103
+ if (typeof window !== "undefined") {
104
+ localStorage.setItem("nocal_auth_session", JSON.stringify({ session, user }));
105
+ }
106
+ resolve({ user, session });
56
107
  }
57
- catch (e) {
58
- return false;
108
+ catch (err) {
109
+ reject(err);
59
110
  }
60
111
  };
61
- const timer = setInterval(async () => {
62
- if (isPopupClosed()) {
63
- clearInterval(timer);
64
- // Sau khi popup đóng, lấy session đã đăng nhập
65
- const { data: sessionData } = await this.supabase.auth.getSession();
66
- if (sessionData?.session?.user) {
67
- resolve(sessionData.session.user);
68
- }
69
- else {
70
- reject(new Error("LOGIN_CANCELLED_OR_FAILED"));
112
+ window.addEventListener("message", messageListener);
113
+ const checkClosedTimer = setInterval(() => {
114
+ try {
115
+ if (popup.closed) {
116
+ clearInterval(checkClosedTimer);
117
+ window.removeEventListener("message", messageListener);
118
+ reject(new Error("POPUP_CLOSED_BY_USER"));
71
119
  }
72
120
  }
121
+ catch (e) {
122
+ // ignore
123
+ }
73
124
  }, 500);
74
125
  });
75
126
  }
76
- /**
77
- * Lấy thông tin User hiện tại
78
- */
127
+ async exchangeCodeForToken(code, codeVerifier) {
128
+ const res = await fetch(`${this.authServerUrl}/api/oauth/token`, {
129
+ method: "POST",
130
+ headers: { "Content-Type": "application/json" },
131
+ body: JSON.stringify({
132
+ grant_type: "authorization_code",
133
+ code,
134
+ client_id: this.clientId,
135
+ redirect_uri: this.redirectUri,
136
+ code_verifier: codeVerifier,
137
+ }),
138
+ });
139
+ const data = await res.json();
140
+ if (!res.ok) {
141
+ throw new Error(data.error_description || data.error || "FAILED_TO_EXCHANGE_TOKEN");
142
+ }
143
+ return data;
144
+ }
145
+ async fetchUserInfo(accessToken) {
146
+ const res = await fetch(`${this.authServerUrl}/api/oauth/userinfo`, {
147
+ headers: {
148
+ Authorization: `Bearer ${accessToken}`,
149
+ },
150
+ });
151
+ const data = await res.json();
152
+ if (!res.ok) {
153
+ throw new Error(data.error_description || data.error || "FAILED_TO_FETCH_USERINFO");
154
+ }
155
+ return data;
156
+ }
79
157
  async getUser() {
80
- const { data: { user }, error, } = await this.supabase.auth.getUser();
81
- if (error)
82
- throw error;
83
- return user;
158
+ return this.currentUser;
84
159
  }
85
- /**
86
- * Lấy Session / Access Token hiện tại
87
- */
88
160
  async getSession() {
89
- const { data: { session }, error, } = await this.supabase.auth.getSession();
90
- if (error)
91
- throw error;
92
- return session;
161
+ return this.currentSession;
93
162
  }
94
- /**
95
- * Đăng xuất khỏi hệ thống
96
- */
97
163
  async signOut() {
98
- const { error } = await this.supabase.auth.signOut();
99
- if (error)
100
- throw error;
101
- }
102
- /**
103
- * Lắng nghe sự kiện đăng nhập / đăng xuất
104
- */
105
- onAuthStateChange(callback) {
106
- return this.supabase.auth.onAuthStateChange(callback);
164
+ this.currentSession = null;
165
+ this.currentUser = null;
166
+ if (typeof window !== "undefined") {
167
+ localStorage.removeItem("nocal_auth_session");
168
+ }
107
169
  }
108
170
  }
109
171
  export * from "./types";
package/dist/types.d.ts CHANGED
@@ -1,11 +1,23 @@
1
- import { User, Session } from "@supabase/supabase-js";
2
1
  export interface NocalClientConfig {
3
2
  clientId: string;
4
3
  redirectUri: string;
4
+ authServerUrl?: string;
5
5
  }
6
- export type NocalOAuthProvider = "google" | "facebook" | "github" | "apple";
7
6
  export interface OAuthLoginReq {
8
- provider?: NocalOAuthProvider;
9
- redirectTo?: string;
7
+ state?: string;
8
+ }
9
+ export interface NocalUser {
10
+ sub: string;
11
+ email?: string;
12
+ phone?: string;
13
+ email_confirmed?: boolean;
14
+ phone_confirmed?: boolean;
15
+ user_metadata?: Record<string, any>;
16
+ [key: string]: any;
17
+ }
18
+ export interface NocalSession {
19
+ access_token: string;
20
+ refresh_token: string;
21
+ token_type: string;
22
+ expires_in: number;
10
23
  }
11
- export type { User, Session };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nocal-auth-sdk",
3
- "version": "0.1.9",
3
+ "version": "0.2.0",
4
4
  "description": "Nocal Auth - client-side SDK for Login with Nocal (popup flow)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",