nocal-auth-sdk 0.2.1 → 0.2.3
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 +182 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +25 -21
- package/dist/types.d.ts +5 -2
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Nocal Auth SDK
|
|
2
|
+
|
|
3
|
+
Client-side SDK để tích hợp đăng nhập bằng Nocal theo flow popup OAuth 2.0 với PKCE.
|
|
4
|
+
|
|
5
|
+
SDK này giúp bạn mở popup đăng nhập, trao đổi authorization code với auth server, lấy thông tin user và lưu session vào localStorage để tiếp tục sử dụng trong frontend.
|
|
6
|
+
|
|
7
|
+
## Tính năng
|
|
8
|
+
|
|
9
|
+
- Đăng nhập popup không cần redirect trang
|
|
10
|
+
- Hỗ trợ OAuth 2.0 + PKCE
|
|
11
|
+
- Tự động lưu session/user vào localStorage
|
|
12
|
+
- Lấy thông tin người dùng sau khi xác thực
|
|
13
|
+
- Tích hợp với Supabase Auth-compatible endpoint mặc định
|
|
14
|
+
|
|
15
|
+
## Cài đặt
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install nocal-auth-sdk
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Hoặc nếu đang dùng trong repo local:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install
|
|
25
|
+
npm run build
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Quick start
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { NocalAuthSDK } from "nocal-auth-sdk";
|
|
32
|
+
|
|
33
|
+
const auth = new NocalAuthSDK({
|
|
34
|
+
clientId: "your-client-id",
|
|
35
|
+
redirectUri: "https://your-app.com/auth/callback",
|
|
36
|
+
authServerUrl: "https://your-auth-server.com",
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
async function login() {
|
|
40
|
+
try {
|
|
41
|
+
const { user, session } = await auth.signInWithNocal();
|
|
42
|
+
console.log("User:", user);
|
|
43
|
+
console.log("Session:", session);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
console.error("Login failed:", error);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Ví dụ dùng trong button click
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
const loginButton = document.getElementById("login-btn");
|
|
54
|
+
|
|
55
|
+
loginButton?.addEventListener("click", async () => {
|
|
56
|
+
try {
|
|
57
|
+
const { user, session } = await auth.signInWithNocal({
|
|
58
|
+
state: "custom-state-value",
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log("Đăng nhập thành công:", user.email || user.phone);
|
|
62
|
+
console.log("Access token:", session.access_token);
|
|
63
|
+
} catch (err) {
|
|
64
|
+
console.error("Lỗi đăng nhập:", err);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Cấu hình
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
interface NocalClientConfig {
|
|
73
|
+
clientId: string;
|
|
74
|
+
redirectUri: string;
|
|
75
|
+
authServerUrl?: string;
|
|
76
|
+
storageKey?: string;
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Giải thích các field
|
|
81
|
+
|
|
82
|
+
- `clientId`: ID client OAuth đã đăng ký với Nocal
|
|
83
|
+
- `redirectUri`: URL callback sau khi auth server redirect về
|
|
84
|
+
- `authServerUrl`: base URL của auth server, mặc định là:
|
|
85
|
+
`https://hldymiruucsqqzicnwdv.supabase.co`
|
|
86
|
+
- `storageKey`: chưa được dùng trong phiên bản hiện tại, session được lưu theo key mặc định `nocal_auth_session`
|
|
87
|
+
|
|
88
|
+
## API
|
|
89
|
+
|
|
90
|
+
### `new NocalAuthSDK(config)`
|
|
91
|
+
|
|
92
|
+
Khởi tạo instance SDK.
|
|
93
|
+
|
|
94
|
+
### `signInWithNocal(req?)`
|
|
95
|
+
|
|
96
|
+
Mở popup OAuth và trả về:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
{
|
|
100
|
+
user: NocalUser;
|
|
101
|
+
session: NocalSession;
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Tham số `req`
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
interface OAuthLoginReq {
|
|
109
|
+
state?: string;
|
|
110
|
+
provider?: string;
|
|
111
|
+
redirectTo?: string;
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `exchangeCodeForToken(code, codeVerifier)`
|
|
116
|
+
|
|
117
|
+
Đổi authorization code lấy access token và refresh token.
|
|
118
|
+
|
|
119
|
+
## Session và User
|
|
120
|
+
|
|
121
|
+
SDK sẽ lưu session và thông tin user vào `localStorage` với key:
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
nocal_auth_session;
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Khi khởi tạo instance, nếu có giá trị trong localStorage thì SDK sẽ tự động restore session hiện tại.
|
|
128
|
+
|
|
129
|
+
## Luồng xử lý OAuth
|
|
130
|
+
|
|
131
|
+
1. Tạo `state` và `codeVerifier`
|
|
132
|
+
2. Tạo `code_challenge` bằng PKCE (`S256`)
|
|
133
|
+
3. Mở popup tới `/auth/v1/oauth/authorize`
|
|
134
|
+
4. Lắng nghe message từ auth server
|
|
135
|
+
5. Nhận `code`
|
|
136
|
+
6. Gọi `/auth/v1/oauth/token` để trao đổi token
|
|
137
|
+
7. Gọi `/auth/v1/oauth/userinfo` để lấy thông tin user
|
|
138
|
+
8. Lưu vào `localStorage` và resolve Promise
|
|
139
|
+
|
|
140
|
+
## Lưu ý quan trọng
|
|
141
|
+
|
|
142
|
+
- Trình duyệt phải cho phép popup mở được
|
|
143
|
+
- `redirectUri` phải khớp với config đã đăng ký trên server
|
|
144
|
+
- Nếu popup bị chặn, SDK ném lỗi `POPUP_BLOCKED`
|
|
145
|
+
- Nếu người dùng đóng popup, SDK ném lỗi `POPUP_CLOSED_BY_USER`
|
|
146
|
+
|
|
147
|
+
## Xử lý lỗi phổ biến
|
|
148
|
+
|
|
149
|
+
- `POPUP_BLOCKED`: popup bị chặn bởi trình duyệt
|
|
150
|
+
- `OAUTH_STATE_MISMATCH`: `state` không khớp
|
|
151
|
+
- `MISSING_AUTHORIZATION_CODE`: không nhận được `code`
|
|
152
|
+
- `FAILED_TO_FETCH_USERINFO`: không lấy được thông tin user
|
|
153
|
+
|
|
154
|
+
## Mẫu type
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
interface NocalUser {
|
|
158
|
+
sub: string;
|
|
159
|
+
email?: string;
|
|
160
|
+
phone?: string;
|
|
161
|
+
email_confirmed?: boolean;
|
|
162
|
+
phone_confirmed?: boolean;
|
|
163
|
+
user_metadata?: Record<string, unknown>;
|
|
164
|
+
[key: string]: unknown;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
interface NocalSession {
|
|
168
|
+
access_token: string;
|
|
169
|
+
refresh_token: string;
|
|
170
|
+
token_type: string;
|
|
171
|
+
expires_in: number;
|
|
172
|
+
expires_at?: number;
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
MIT
|
|
179
|
+
|
|
180
|
+
## Author
|
|
181
|
+
|
|
182
|
+
Nocal Auth SDK
|
package/dist/index.d.ts
CHANGED
|
@@ -13,7 +13,7 @@ export declare class NocalAuthSDK {
|
|
|
13
13
|
user: NocalUser;
|
|
14
14
|
session: NocalSession;
|
|
15
15
|
}>;
|
|
16
|
-
|
|
16
|
+
exchangeCodeForToken(code: string, codeVerifier: string): Promise<NocalSession>;
|
|
17
17
|
private fetchUserInfo;
|
|
18
18
|
getUser(): Promise<NocalUser | null>;
|
|
19
19
|
getSession(): Promise<NocalSession | null>;
|
package/dist/index.js
CHANGED
|
@@ -60,8 +60,12 @@ export class NocalAuthSDK {
|
|
|
60
60
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
61
61
|
const width = 480;
|
|
62
62
|
const height = 680;
|
|
63
|
-
const left = typeof window !== "undefined"
|
|
64
|
-
|
|
63
|
+
const left = typeof window !== "undefined"
|
|
64
|
+
? window.screenX + (window.outerWidth - width) / 2
|
|
65
|
+
: 0;
|
|
66
|
+
const top = typeof window !== "undefined"
|
|
67
|
+
? window.screenY + (window.outerHeight - height) / 2
|
|
68
|
+
: 0;
|
|
65
69
|
const popup = window.open(authUrl.toString(), "NocalAuthPopup", `width=${width},height=${height},left=${left},top=${top},status=yes,scrollbars=yes`);
|
|
66
70
|
if (!popup) {
|
|
67
71
|
throw new Error("POPUP_BLOCKED");
|
|
@@ -72,8 +76,9 @@ export class NocalAuthSDK {
|
|
|
72
76
|
if (event.origin !== allowedOrigin)
|
|
73
77
|
return;
|
|
74
78
|
const data = event.data;
|
|
75
|
-
if (!data ||
|
|
76
|
-
|
|
79
|
+
if (!data ||
|
|
80
|
+
(data.type !== "NOCAL_OAUTH_CODE" &&
|
|
81
|
+
data.type !== "NOCAL_OAUTH_ERROR")) {
|
|
77
82
|
return;
|
|
78
83
|
}
|
|
79
84
|
window.removeEventListener("message", messageListener);
|
|
@@ -115,17 +120,14 @@ export class NocalAuthSDK {
|
|
|
115
120
|
if (popup.closed) {
|
|
116
121
|
clearInterval(checkClosedTimer);
|
|
117
122
|
window.removeEventListener("message", messageListener);
|
|
118
|
-
// Kiểm tra nếu popup đóng và đã có code trong redirect URL (nếu dùng redirect thuần)
|
|
119
123
|
reject(new Error("POPUP_CLOSED_BY_USER"));
|
|
120
124
|
}
|
|
121
125
|
else {
|
|
122
|
-
// Lắng nghe URL thay đổi nếu Supabase redirect trực tiếp về redirectUri
|
|
123
126
|
try {
|
|
124
127
|
const popupUrl = new URL(popup.location.href);
|
|
125
128
|
if (popupUrl.origin === new URL(this.redirectUri).origin) {
|
|
126
129
|
const code = popupUrl.searchParams.get("code");
|
|
127
130
|
const error = popupUrl.searchParams.get("error");
|
|
128
|
-
const returnedState = popupUrl.searchParams.get("state");
|
|
129
131
|
if (code || error) {
|
|
130
132
|
clearInterval(checkClosedTimer);
|
|
131
133
|
window.removeEventListener("message", messageListener);
|
|
@@ -151,7 +153,7 @@ export class NocalAuthSDK {
|
|
|
151
153
|
}
|
|
152
154
|
}
|
|
153
155
|
catch (e) {
|
|
154
|
-
// Cross-origin check
|
|
156
|
+
// Cross-origin check
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
}
|
|
@@ -162,22 +164,24 @@ export class NocalAuthSDK {
|
|
|
162
164
|
});
|
|
163
165
|
}
|
|
164
166
|
async exchangeCodeForToken(code, codeVerifier) {
|
|
165
|
-
const
|
|
167
|
+
const params = new URLSearchParams();
|
|
168
|
+
params.set("grant_type", "authorization_code");
|
|
169
|
+
params.set("client_id", this.clientId);
|
|
170
|
+
params.set("redirect_uri", this.redirectUri);
|
|
171
|
+
params.set("code", code);
|
|
172
|
+
params.set("code_verifier", codeVerifier); // PKCE xác thực thay cho secret
|
|
173
|
+
const response = await fetch(`${this.authServerUrl}/auth/v1/oauth/token`, {
|
|
166
174
|
method: "POST",
|
|
167
|
-
headers: {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
client_id: this.clientId,
|
|
172
|
-
redirect_uri: this.redirectUri,
|
|
173
|
-
code_verifier: codeVerifier,
|
|
174
|
-
}),
|
|
175
|
+
headers: {
|
|
176
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
177
|
+
},
|
|
178
|
+
body: params.toString(),
|
|
175
179
|
});
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
throw new Error(
|
|
180
|
+
if (!response.ok) {
|
|
181
|
+
const errorData = await response.json();
|
|
182
|
+
throw new Error(`Supabase Auth Error: ${errorData.error_description || errorData.error}`);
|
|
179
183
|
}
|
|
180
|
-
return
|
|
184
|
+
return (await response.json());
|
|
181
185
|
}
|
|
182
186
|
async fetchUserInfo(accessToken) {
|
|
183
187
|
const res = await fetch(`${this.authServerUrl}/auth/v1/oauth/userinfo`, {
|
package/dist/types.d.ts
CHANGED
|
@@ -5,6 +5,8 @@ export interface NocalClientConfig {
|
|
|
5
5
|
}
|
|
6
6
|
export interface OAuthLoginReq {
|
|
7
7
|
state?: string;
|
|
8
|
+
provider?: string;
|
|
9
|
+
redirectTo?: string;
|
|
8
10
|
}
|
|
9
11
|
export interface NocalUser {
|
|
10
12
|
sub: string;
|
|
@@ -12,12 +14,13 @@ export interface NocalUser {
|
|
|
12
14
|
phone?: string;
|
|
13
15
|
email_confirmed?: boolean;
|
|
14
16
|
phone_confirmed?: boolean;
|
|
15
|
-
user_metadata?: Record<string,
|
|
16
|
-
[key: string]:
|
|
17
|
+
user_metadata?: Record<string, unknown>;
|
|
18
|
+
[key: string]: unknown;
|
|
17
19
|
}
|
|
18
20
|
export interface NocalSession {
|
|
19
21
|
access_token: string;
|
|
20
22
|
refresh_token: string;
|
|
21
23
|
token_type: string;
|
|
22
24
|
expires_in: number;
|
|
25
|
+
expires_at?: number;
|
|
23
26
|
}
|