@basictech/react 0.7.0-beta.2 → 0.7.0-beta.4
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/.turbo/turbo-build.log +10 -10
- package/AUTH_IMPLEMENTATION_GUIDE.md +2009 -0
- package/changelog.md +12 -0
- package/dist/index.d.mts +19 -113
- package/dist/index.d.ts +19 -113
- package/dist/index.js +62 -387
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +62 -386
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/AuthContext.tsx +82 -18
- package/src/index.ts +4 -4
- package/src/utils/storage.ts +1 -0
package/src/AuthContext.tsx
CHANGED
|
@@ -13,6 +13,25 @@ import { getSchemaStatus, validateAndCheckSchema } from './utils/schema'
|
|
|
13
13
|
|
|
14
14
|
export type { BasicStorage, LocalStorageAdapter } from './utils/storage'
|
|
15
15
|
|
|
16
|
+
export type AuthConfig = {
|
|
17
|
+
scopes?: string | string[];
|
|
18
|
+
server_url?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type BasicProviderProps = {
|
|
22
|
+
children: React.ReactNode;
|
|
23
|
+
project_id?: string;
|
|
24
|
+
schema?: any;
|
|
25
|
+
debug?: boolean;
|
|
26
|
+
storage?: BasicStorage;
|
|
27
|
+
auth?: AuthConfig;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DEFAULT_AUTH_CONFIG: Required<AuthConfig> = {
|
|
31
|
+
scopes: 'profile email app:admin',
|
|
32
|
+
server_url: 'https://api.basic.tech'
|
|
33
|
+
}
|
|
34
|
+
|
|
16
35
|
|
|
17
36
|
type BasicSyncType = {
|
|
18
37
|
basic_schema: any;
|
|
@@ -90,14 +109,9 @@ export function BasicProvider({
|
|
|
90
109
|
project_id,
|
|
91
110
|
schema,
|
|
92
111
|
debug = false,
|
|
93
|
-
storage
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
project_id?: string,
|
|
97
|
-
schema?: any,
|
|
98
|
-
debug?: boolean,
|
|
99
|
-
storage?: BasicStorage
|
|
100
|
-
}) {
|
|
112
|
+
storage,
|
|
113
|
+
auth
|
|
114
|
+
}: BasicProviderProps) {
|
|
101
115
|
const [isAuthReady, setIsAuthReady] = useState(false)
|
|
102
116
|
const [isSignedIn, setIsSignedIn] = useState<boolean>(false)
|
|
103
117
|
const [token, setToken] = useState<Token | null>(null)
|
|
@@ -112,6 +126,17 @@ export function BasicProvider({
|
|
|
112
126
|
|
|
113
127
|
const syncRef = useRef<BasicSync | null>(null);
|
|
114
128
|
const storageAdapter = storage || new LocalStorageAdapter();
|
|
129
|
+
|
|
130
|
+
// Merge auth config with defaults
|
|
131
|
+
const authConfig: Required<AuthConfig> = {
|
|
132
|
+
scopes: auth?.scopes || DEFAULT_AUTH_CONFIG.scopes,
|
|
133
|
+
server_url: auth?.server_url || DEFAULT_AUTH_CONFIG.server_url
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Normalize scopes to space-separated string
|
|
137
|
+
const scopesString = Array.isArray(authConfig.scopes)
|
|
138
|
+
? authConfig.scopes.join(' ')
|
|
139
|
+
: authConfig.scopes;
|
|
115
140
|
|
|
116
141
|
const isDevMode = () => isDevelopment(debug)
|
|
117
142
|
|
|
@@ -312,7 +337,7 @@ export function BasicProvider({
|
|
|
312
337
|
useEffect(() => {
|
|
313
338
|
async function fetchUser(acc_token: string) {
|
|
314
339
|
console.info('fetching user')
|
|
315
|
-
const user = await fetch(
|
|
340
|
+
const user = await fetch(`${authConfig.server_url}/auth/userInfo`, {
|
|
316
341
|
method: 'GET',
|
|
317
342
|
headers: {
|
|
318
343
|
'Authorization': `Bearer ${acc_token}`
|
|
@@ -395,14 +420,18 @@ export function BasicProvider({
|
|
|
395
420
|
throw new Error('Invalid redirect URI provided')
|
|
396
421
|
}
|
|
397
422
|
|
|
398
|
-
|
|
423
|
+
// Store redirect_uri for token exchange
|
|
424
|
+
await storageAdapter.set(STORAGE_KEYS.REDIRECT_URI, redirectUrl)
|
|
425
|
+
log('Stored redirect_uri for token exchange:', redirectUrl)
|
|
426
|
+
|
|
427
|
+
let baseUrl = `${authConfig.server_url}/auth/authorize`
|
|
399
428
|
baseUrl += `?client_id=${project_id}`
|
|
400
429
|
baseUrl += `&redirect_uri=${encodeURIComponent(redirectUrl)}`
|
|
401
430
|
baseUrl += `&response_type=code`
|
|
402
|
-
baseUrl += `&scope
|
|
431
|
+
baseUrl += `&scope=${encodeURIComponent(scopesString)}`
|
|
403
432
|
baseUrl += `&state=${randomState}`
|
|
404
433
|
|
|
405
|
-
log('Generated sign-in link successfully')
|
|
434
|
+
log('Generated sign-in link successfully with scopes:', scopesString)
|
|
406
435
|
return baseUrl;
|
|
407
436
|
|
|
408
437
|
} catch (error) {
|
|
@@ -492,6 +521,7 @@ export function BasicProvider({
|
|
|
492
521
|
await storageAdapter.remove(STORAGE_KEYS.AUTH_STATE)
|
|
493
522
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN)
|
|
494
523
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO)
|
|
524
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI)
|
|
495
525
|
if (syncRef.current) {
|
|
496
526
|
(async () => {
|
|
497
527
|
try {
|
|
@@ -574,17 +604,43 @@ export function BasicProvider({
|
|
|
574
604
|
throw new Error('Network offline - refresh will be retried when online')
|
|
575
605
|
}
|
|
576
606
|
|
|
577
|
-
|
|
578
|
-
|
|
607
|
+
let requestBody: any
|
|
608
|
+
|
|
609
|
+
if (isRefreshToken) {
|
|
610
|
+
// Refresh token request
|
|
611
|
+
requestBody = {
|
|
579
612
|
grant_type: 'refresh_token',
|
|
580
|
-
refresh_token: codeOrRefreshToken
|
|
613
|
+
refresh_token: codeOrRefreshToken
|
|
581
614
|
}
|
|
582
|
-
|
|
615
|
+
// Include client_id if available for validation
|
|
616
|
+
if (project_id) {
|
|
617
|
+
requestBody.client_id = project_id
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
// Authorization code exchange
|
|
621
|
+
requestBody = {
|
|
583
622
|
grant_type: 'authorization_code',
|
|
584
|
-
code: codeOrRefreshToken
|
|
623
|
+
code: codeOrRefreshToken
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
// Retrieve stored redirect_uri (required by OAuth2 spec)
|
|
627
|
+
const storedRedirectUri = await storageAdapter.get(STORAGE_KEYS.REDIRECT_URI)
|
|
628
|
+
if (storedRedirectUri) {
|
|
629
|
+
requestBody.redirect_uri = storedRedirectUri
|
|
630
|
+
log('Including redirect_uri in token exchange:', storedRedirectUri)
|
|
631
|
+
} else {
|
|
632
|
+
log('Warning: No redirect_uri found in storage for token exchange')
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Include client_id for validation
|
|
636
|
+
if (project_id) {
|
|
637
|
+
requestBody.client_id = project_id
|
|
585
638
|
}
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
log('Token exchange request body:', { ...requestBody, refresh_token: isRefreshToken ? '[REDACTED]' : undefined, code: !isRefreshToken ? '[REDACTED]' : undefined })
|
|
586
642
|
|
|
587
|
-
const token = await fetch(
|
|
643
|
+
const token = await fetch(`${authConfig.server_url}/auth/token`, {
|
|
588
644
|
method: 'POST',
|
|
589
645
|
headers: {
|
|
590
646
|
'Content-Type': 'application/json'
|
|
@@ -611,6 +667,7 @@ export function BasicProvider({
|
|
|
611
667
|
|
|
612
668
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN)
|
|
613
669
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO)
|
|
670
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI)
|
|
614
671
|
clearCookie('basic_token');
|
|
615
672
|
clearCookie('basic_access_token');
|
|
616
673
|
|
|
@@ -629,6 +686,12 @@ export function BasicProvider({
|
|
|
629
686
|
log('Updated refresh token in storage')
|
|
630
687
|
}
|
|
631
688
|
|
|
689
|
+
// Clean up redirect_uri after successful token exchange
|
|
690
|
+
if (!isRefreshToken) {
|
|
691
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI)
|
|
692
|
+
log('Cleaned up redirect_uri from storage after successful exchange')
|
|
693
|
+
}
|
|
694
|
+
|
|
632
695
|
setCookie('basic_access_token', token.access_token, { httpOnly: false });
|
|
633
696
|
log('Updated access token in cookie')
|
|
634
697
|
}
|
|
@@ -639,6 +702,7 @@ export function BasicProvider({
|
|
|
639
702
|
if (!(error as Error).message.includes('offline') && !(error as Error).message.includes('Network')) {
|
|
640
703
|
await storageAdapter.remove(STORAGE_KEYS.REFRESH_TOKEN)
|
|
641
704
|
await storageAdapter.remove(STORAGE_KEYS.USER_INFO)
|
|
705
|
+
await storageAdapter.remove(STORAGE_KEYS.REDIRECT_URI)
|
|
642
706
|
clearCookie('basic_token');
|
|
643
707
|
clearCookie('basic_access_token');
|
|
644
708
|
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useState } from "react";
|
|
2
|
-
import { useBasic, BasicProvider, BasicStorage, LocalStorageAdapter } from "./AuthContext";
|
|
2
|
+
import { useBasic, BasicProvider, BasicStorage, LocalStorageAdapter, AuthConfig, BasicProviderProps } from "./AuthContext";
|
|
3
3
|
import { useLiveQuery as useQuery } from "dexie-react-hooks";
|
|
4
4
|
// import { createVersionUpdater, VersionUpdater, Migration } from "./versionUpdater";
|
|
5
5
|
|
|
@@ -8,6 +8,6 @@ export {
|
|
|
8
8
|
useBasic, BasicProvider, useQuery
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
export type {
|
|
12
|
+
AuthConfig, BasicStorage, LocalStorageAdapter, BasicProviderProps
|
|
13
|
+
}
|