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