@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.
@@ -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('https://api.basic.tech/auth/userInfo', {
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
- let baseUrl = "https://api.basic.tech/auth/authorize"
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=profile`
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
- const requestBody = isRefreshToken
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('https://api.basic.tech/auth/token', {
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
  // }
@@ -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