@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.
@@ -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
- children: React.ReactNode,
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('https://api.basic.tech/auth/userInfo', {
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
- let baseUrl = "https://api.basic.tech/auth/authorize"
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=profile`
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
- const requestBody = isRefreshToken
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('https://api.basic.tech/auth/token', {
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
- // export type {
12
- // VersionUpdater, Migration
13
- // }
11
+ export type {
12
+ AuthConfig, BasicStorage, LocalStorageAdapter, BasicProviderProps
13
+ }
@@ -23,6 +23,7 @@ export const STORAGE_KEYS = {
23
23
  REFRESH_TOKEN: 'basic_refresh_token',
24
24
  USER_INFO: 'basic_user_info',
25
25
  AUTH_STATE: 'basic_auth_state',
26
+ REDIRECT_URI: 'basic_redirect_uri',
26
27
  DEBUG: 'basic_debug'
27
28
  } as const
28
29