@authdog/react-native 0.2.0
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 +144 -0
- package/dist/index.d.mts +279 -0
- package/dist/index.d.ts +279 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +57 -0
package/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @authdog/react-native
|
|
2
|
+
|
|
3
|
+
**Authdog SDK for React Native & Expo** — secure session management, hosted
|
|
4
|
+
login via deep-linking, and user/permission hooks for your mobile app.
|
|
5
|
+
|
|
6
|
+
- 🔐 **Secure by default** — token validated as a JWT before storage; identity
|
|
7
|
+
host constrained to the Authdog trusted-host allowlist (no token exfiltration).
|
|
8
|
+
- 📦 **Pluggable storage** — bring `expo-secure-store`, AsyncStorage, or your own
|
|
9
|
+
async store. No hard Expo dependency.
|
|
10
|
+
- 🪝 **Familiar hooks** — `useUser`, `useSession`, `useSignIn`, `useSignOut`, and
|
|
11
|
+
more, mirroring the rest of the Authdog Web SDK.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
bun add @authdog/react-native
|
|
17
|
+
# secure, hardware-backed token storage (recommended)
|
|
18
|
+
npx expo install expo-secure-store
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
`react` and `react-native` are peer dependencies.
|
|
22
|
+
|
|
23
|
+
## Quick start
|
|
24
|
+
|
|
25
|
+
Wrap your app in the provider, backed by a secure store:
|
|
26
|
+
|
|
27
|
+
```tsx
|
|
28
|
+
import * as SecureStore from "expo-secure-store";
|
|
29
|
+
import {
|
|
30
|
+
AuthdogProvider,
|
|
31
|
+
createSecureStoreAdapter,
|
|
32
|
+
} from "@authdog/react-native";
|
|
33
|
+
|
|
34
|
+
const PUBLIC_KEY = process.env.EXPO_PUBLIC_PK_AUTHDOG!; // pk_…
|
|
35
|
+
|
|
36
|
+
export default function App() {
|
|
37
|
+
return (
|
|
38
|
+
<AuthdogProvider
|
|
39
|
+
publicKey={PUBLIC_KEY}
|
|
40
|
+
storage={createSecureStoreAdapter(SecureStore)}
|
|
41
|
+
>
|
|
42
|
+
<RootNavigator />
|
|
43
|
+
</AuthdogProvider>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Without a `storage` prop the provider falls back to an in-memory store — fine
|
|
49
|
+
for prototyping, but the token is lost on restart and is not encrypted at rest.
|
|
50
|
+
|
|
51
|
+
## Signing in (deep-linking)
|
|
52
|
+
|
|
53
|
+
Open the hosted login flow and complete it from the deep link the identity
|
|
54
|
+
server redirects back to:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
import { useEffect } from "react";
|
|
58
|
+
import { Linking } from "react-native";
|
|
59
|
+
import { useSignIn, useRedirectHandler, useSession } from "@authdog/react-native";
|
|
60
|
+
|
|
61
|
+
// Use a deep link your app handles. With Expo: Linking.createURL("/callback").
|
|
62
|
+
const REDIRECT_URL = "myapp://callback";
|
|
63
|
+
|
|
64
|
+
export function SignInScreen() {
|
|
65
|
+
const { signIn } = useSignIn();
|
|
66
|
+
const { handleRedirect } = useRedirectHandler();
|
|
67
|
+
const { session } = useSession();
|
|
68
|
+
|
|
69
|
+
useEffect(() => {
|
|
70
|
+
// Cold start (app opened via the link).
|
|
71
|
+
Linking.getInitialURL().then((url) => url && handleRedirect(url));
|
|
72
|
+
// Warm start (already running).
|
|
73
|
+
const sub = Linking.addEventListener("url", ({ url }) =>
|
|
74
|
+
handleRedirect(url),
|
|
75
|
+
);
|
|
76
|
+
return () => sub.remove();
|
|
77
|
+
}, [handleRedirect]);
|
|
78
|
+
|
|
79
|
+
if (session.isAuthenticated) return <Text>Signed in 🎉</Text>;
|
|
80
|
+
|
|
81
|
+
return <Button title="Sign in" onPress={() => signIn(REDIRECT_URL)} />;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`handleRedirect` extracts the `?token=` value, **validates it as a JWT before
|
|
86
|
+
persisting it**, and updates the session — a crafted deep link cannot write
|
|
87
|
+
arbitrary data into secure storage. Use `useSignUp().signUp(REDIRECT_URL)` for
|
|
88
|
+
the sign-up flow.
|
|
89
|
+
|
|
90
|
+
## Reading the user
|
|
91
|
+
|
|
92
|
+
```tsx
|
|
93
|
+
import { useEffect } from "react";
|
|
94
|
+
import { useUser } from "@authdog/react-native";
|
|
95
|
+
|
|
96
|
+
export function Profile() {
|
|
97
|
+
const { user, fetchUser, isLoading } = useUser();
|
|
98
|
+
|
|
99
|
+
useEffect(() => {
|
|
100
|
+
fetchUser();
|
|
101
|
+
}, [fetchUser]);
|
|
102
|
+
|
|
103
|
+
if (isLoading) return <ActivityIndicator />;
|
|
104
|
+
return <Text>{JSON.stringify(user)}</Text>;
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Signing out
|
|
109
|
+
|
|
110
|
+
```tsx
|
|
111
|
+
const { signOut } = useSignOut();
|
|
112
|
+
// clears the token from state and storage; navigate as you see fit
|
|
113
|
+
await signOut();
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Permissions (UI hints only)
|
|
117
|
+
|
|
118
|
+
`useAuthz` fetches a permission list to drive UI affordances. **It is
|
|
119
|
+
presentational only and trivially bypassable** — never use it as an
|
|
120
|
+
access-control check. Enforce every protected operation server-side.
|
|
121
|
+
|
|
122
|
+
```tsx
|
|
123
|
+
const { fetchPermissions, hasPermission } = useAuthz({
|
|
124
|
+
permissionsUrl: "https://api.example.com/permissions",
|
|
125
|
+
});
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## API
|
|
129
|
+
|
|
130
|
+
| Export | Description |
|
|
131
|
+
| ------ | ----------- |
|
|
132
|
+
| `AuthdogProvider` | Context provider. Props: `publicKey`, `storage?`. |
|
|
133
|
+
| `useSession` | `{ session: { token, isAuthenticated }, isLoading }`. |
|
|
134
|
+
| `useUser` | `{ user, fetchUser, isAuthenticated, isLoading, error }`. |
|
|
135
|
+
| `useSignIn` / `useSignUp` | `{ signIn/signUp(redirectUrl), isLoading, error }`. |
|
|
136
|
+
| `useSignOut` | `{ signOut(), isLoading, error }`. |
|
|
137
|
+
| `useRedirectHandler` | `{ handleRedirect(url) }` — completes login from a deep link. |
|
|
138
|
+
| `useAuthz` | UI-only permission helpers. |
|
|
139
|
+
| `createSecureStoreAdapter` | Adapts `expo-secure-store` to `AuthdogStorage`. |
|
|
140
|
+
| `inMemoryStorage` | Non-persistent default store. |
|
|
141
|
+
|
|
142
|
+
## License
|
|
143
|
+
|
|
144
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { PublicKeyPayload } from '@authdog/node-commons';
|
|
4
|
+
export { PublicKeyPayload } from '@authdog/node-commons';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pluggable, asynchronous token storage.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the browser SDKs (which can read `localStorage` synchronously), mobile
|
|
10
|
+
* secure storage is always asynchronous. The SDK therefore talks to storage
|
|
11
|
+
* exclusively through this async interface so any backing store can be used:
|
|
12
|
+
* `expo-secure-store`, `@react-native-async-storage/async-storage`, the
|
|
13
|
+
* Keychain/Keystore, etc.
|
|
14
|
+
*
|
|
15
|
+
* For real apps you SHOULD use a hardware-backed secure store
|
|
16
|
+
* (`expo-secure-store`) so the session token is encrypted at rest rather than
|
|
17
|
+
* sitting in plain AsyncStorage.
|
|
18
|
+
*/
|
|
19
|
+
interface AuthdogStorage {
|
|
20
|
+
getItem: (key: string) => Promise<string | null>;
|
|
21
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
22
|
+
removeItem: (key: string) => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Minimal in-memory storage. Intended as a safe default and for tests — the
|
|
26
|
+
* token does NOT survive an app restart and is NOT encrypted at rest. Provide
|
|
27
|
+
* a real `AuthdogStorage` (e.g. via {@link createSecureStoreAdapter}) in
|
|
28
|
+
* production.
|
|
29
|
+
*/
|
|
30
|
+
declare const inMemoryStorage: () => AuthdogStorage;
|
|
31
|
+
/**
|
|
32
|
+
* The subset of the `expo-secure-store` module the adapter relies on. Declared
|
|
33
|
+
* structurally so this package never has to depend on Expo directly.
|
|
34
|
+
*/
|
|
35
|
+
interface SecureStoreLike {
|
|
36
|
+
getItemAsync: (key: string) => Promise<string | null>;
|
|
37
|
+
setItemAsync: (key: string, value: string) => Promise<void>;
|
|
38
|
+
deleteItemAsync: (key: string) => Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Adapts the `expo-secure-store` module to {@link AuthdogStorage}.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* import * as SecureStore from "expo-secure-store";
|
|
45
|
+
* import { createSecureStoreAdapter } from "@authdog/react-native";
|
|
46
|
+
*
|
|
47
|
+
* const storage = createSecureStoreAdapter(SecureStore);
|
|
48
|
+
*/
|
|
49
|
+
declare const createSecureStoreAdapter: (secureStore: SecureStoreLike) => AuthdogStorage;
|
|
50
|
+
|
|
51
|
+
interface AuthdogContextValue {
|
|
52
|
+
/** The Authdog public key (`pk_…`) the app was configured with. */
|
|
53
|
+
readonly publicKey: string;
|
|
54
|
+
/** `true` while the persisted token is being loaded from storage on mount. */
|
|
55
|
+
readonly isLoading: boolean;
|
|
56
|
+
/** The current session token, or `null` when signed out. */
|
|
57
|
+
readonly token: string | null;
|
|
58
|
+
/** Async-aware token storage backing this provider. */
|
|
59
|
+
readonly storage: AuthdogStorage;
|
|
60
|
+
/** The storage key the session token is persisted under. */
|
|
61
|
+
readonly storageKey: string;
|
|
62
|
+
/**
|
|
63
|
+
* Persists (or clears, when passed `null`) the token in storage and updates
|
|
64
|
+
* the in-memory state. The returned promise resolves once storage settles.
|
|
65
|
+
*/
|
|
66
|
+
setToken: (token: string | null) => Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
declare const AuthdogContext: react.Context<AuthdogContextValue | null>;
|
|
69
|
+
interface AuthdogProviderProps {
|
|
70
|
+
/** Your Authdog public key (`pk_…`). */
|
|
71
|
+
publicKey: string;
|
|
72
|
+
/**
|
|
73
|
+
* Token storage. Defaults to an in-memory store (token is lost on restart).
|
|
74
|
+
* Provide a secure store in production — see `createSecureStoreAdapter`.
|
|
75
|
+
*/
|
|
76
|
+
storage?: AuthdogStorage;
|
|
77
|
+
children?: ReactNode;
|
|
78
|
+
}
|
|
79
|
+
declare const AuthdogProvider: ({ publicKey, storage, children, }: AuthdogProviderProps) => react.JSX.Element;
|
|
80
|
+
|
|
81
|
+
declare const useSession: () => {
|
|
82
|
+
session: {
|
|
83
|
+
token: string | null;
|
|
84
|
+
isAuthenticated: boolean;
|
|
85
|
+
};
|
|
86
|
+
isLoading: boolean;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
declare const useUser: () => {
|
|
90
|
+
user: unknown;
|
|
91
|
+
isLoading: boolean;
|
|
92
|
+
error: Error | null;
|
|
93
|
+
isAuthenticated: boolean;
|
|
94
|
+
fetchUser: () => Promise<{
|
|
95
|
+
id: string;
|
|
96
|
+
environmentId: string;
|
|
97
|
+
externalId: string;
|
|
98
|
+
userName: string;
|
|
99
|
+
displayName: string;
|
|
100
|
+
nickName: string;
|
|
101
|
+
profileUrl: string;
|
|
102
|
+
title: string;
|
|
103
|
+
userType: string;
|
|
104
|
+
preferredLanguage: string | null;
|
|
105
|
+
locale: string | null;
|
|
106
|
+
timezone: string | null;
|
|
107
|
+
active: boolean;
|
|
108
|
+
provider: string;
|
|
109
|
+
lastLogin: string;
|
|
110
|
+
createdAt: string;
|
|
111
|
+
updatedAt: string;
|
|
112
|
+
names: {
|
|
113
|
+
id: string;
|
|
114
|
+
userId: string;
|
|
115
|
+
formatted: string | null;
|
|
116
|
+
familyName: string;
|
|
117
|
+
givenName: string;
|
|
118
|
+
middleName: string | null;
|
|
119
|
+
honorificPrefix: string | null;
|
|
120
|
+
honorificSuffix: string | null;
|
|
121
|
+
createdAt: string;
|
|
122
|
+
updatedAt: string;
|
|
123
|
+
};
|
|
124
|
+
addresses: [];
|
|
125
|
+
emails: {
|
|
126
|
+
value: string;
|
|
127
|
+
primary: boolean;
|
|
128
|
+
type: string;
|
|
129
|
+
}[];
|
|
130
|
+
phoneNumbers: [];
|
|
131
|
+
ims: [];
|
|
132
|
+
photos: {
|
|
133
|
+
value: string;
|
|
134
|
+
type: string;
|
|
135
|
+
}[];
|
|
136
|
+
} | null>;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
declare const useSignIn: () => {
|
|
140
|
+
signIn: (redirectUrl: string) => Promise<void>;
|
|
141
|
+
isLoading: boolean;
|
|
142
|
+
error: Error | null;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
declare const useSignUp: () => {
|
|
146
|
+
signUp: (redirectUrl: string) => Promise<void>;
|
|
147
|
+
isLoading: boolean;
|
|
148
|
+
error: Error | null;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
declare const useSignOut: () => {
|
|
152
|
+
signOut: () => Promise<void>;
|
|
153
|
+
isLoading: boolean;
|
|
154
|
+
error: Error | null;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
declare const useRedirectHandler: () => {
|
|
158
|
+
handleRedirect: (url: string) => Promise<string | null>;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* ⚠️ PRESENTATIONAL ONLY — NOT A SECURITY BOUNDARY.
|
|
163
|
+
*
|
|
164
|
+
* This hook fetches a permission list to drive UI affordances (showing or
|
|
165
|
+
* hiding buttons, tabs, screens, etc.). It runs entirely on the device and is
|
|
166
|
+
* therefore trivially bypassable by anyone who controls the client. It MUST
|
|
167
|
+
* NOT be used to gate access to data or actions.
|
|
168
|
+
*
|
|
169
|
+
* Every protected operation MUST be independently enforced server-side.
|
|
170
|
+
*/
|
|
171
|
+
interface UseAuthzOptions {
|
|
172
|
+
/**
|
|
173
|
+
* Absolute URL of the endpoint that returns the current user's permissions.
|
|
174
|
+
* Defaults to "/api/permissions" — note that on native you almost always
|
|
175
|
+
* need a fully-qualified URL. This endpoint is informational only;
|
|
176
|
+
* authorization must still be enforced on every protected server endpoint.
|
|
177
|
+
*/
|
|
178
|
+
permissionsUrl?: string;
|
|
179
|
+
}
|
|
180
|
+
declare const useAuthz: (options?: UseAuthzOptions) => {
|
|
181
|
+
permissions: string[];
|
|
182
|
+
isLoading: boolean;
|
|
183
|
+
error: Error | null;
|
|
184
|
+
fetchPermissions: () => Promise<string[]>;
|
|
185
|
+
hasPermission: (permission: string) => boolean;
|
|
186
|
+
hasAnyPermission: (list: string[]) => boolean;
|
|
187
|
+
hasAllPermissions: (list: string[]) => boolean;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
interface BuildAuthorizeUrlOptions {
|
|
191
|
+
/**
|
|
192
|
+
* The app's deep-link/callback URL the identity server redirects back to,
|
|
193
|
+
* e.g. `myapp://callback`. This is registered as the OIDC `redirect_uri`.
|
|
194
|
+
*/
|
|
195
|
+
redirectUrl: string;
|
|
196
|
+
/** When `true`, hints the hosted UI to show the sign-up flow. */
|
|
197
|
+
signup?: boolean;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Builds the hosted OIDC authorize URL for the given public key.
|
|
201
|
+
*
|
|
202
|
+
* The identity host comes from the validated public key payload (constrained to
|
|
203
|
+
* the trusted-host allowlist by the shared parser), so a crafted key cannot
|
|
204
|
+
* point the login flow at an attacker-controlled origin.
|
|
205
|
+
*/
|
|
206
|
+
declare const buildAuthorizeUrl: (publicKey: string, { redirectUrl, signup }: BuildAuthorizeUrlOptions) => string;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Decodes and validates an Authdog public key. Delegates to the hardened
|
|
210
|
+
* shared parser in @authdog/node-commons, which validates the payload and
|
|
211
|
+
* enforces a trusted identity-host allowlist (SSRF / token-exfiltration
|
|
212
|
+
* protection) rather than blindly decoding base64/JSON.
|
|
213
|
+
*/
|
|
214
|
+
declare const getPublicKeyPayload: (publicKey: string) => PublicKeyPayload;
|
|
215
|
+
/**
|
|
216
|
+
* Extracts the `?token=` query parameter from a redirect/deep-link URL.
|
|
217
|
+
* Returns `null` when the URL is malformed or carries no token.
|
|
218
|
+
*/
|
|
219
|
+
declare const getTokenFromUri: (url: string) => string | null;
|
|
220
|
+
|
|
221
|
+
interface IFetchUserData {
|
|
222
|
+
user: {
|
|
223
|
+
id: string;
|
|
224
|
+
environmentId: string;
|
|
225
|
+
externalId: string;
|
|
226
|
+
userName: string;
|
|
227
|
+
displayName: string;
|
|
228
|
+
nickName: string;
|
|
229
|
+
profileUrl: string;
|
|
230
|
+
title: string;
|
|
231
|
+
userType: string;
|
|
232
|
+
preferredLanguage: string | null;
|
|
233
|
+
locale: string | null;
|
|
234
|
+
timezone: string | null;
|
|
235
|
+
active: boolean;
|
|
236
|
+
provider: string;
|
|
237
|
+
lastLogin: string;
|
|
238
|
+
createdAt: string;
|
|
239
|
+
updatedAt: string;
|
|
240
|
+
names: {
|
|
241
|
+
id: string;
|
|
242
|
+
userId: string;
|
|
243
|
+
formatted: string | null;
|
|
244
|
+
familyName: string;
|
|
245
|
+
givenName: string;
|
|
246
|
+
middleName: string | null;
|
|
247
|
+
honorificPrefix: string | null;
|
|
248
|
+
honorificSuffix: string | null;
|
|
249
|
+
createdAt: string;
|
|
250
|
+
updatedAt: string;
|
|
251
|
+
};
|
|
252
|
+
addresses: [];
|
|
253
|
+
emails: {
|
|
254
|
+
value: string;
|
|
255
|
+
primary: boolean;
|
|
256
|
+
type: string;
|
|
257
|
+
}[];
|
|
258
|
+
phoneNumbers: [];
|
|
259
|
+
ims: [];
|
|
260
|
+
photos: {
|
|
261
|
+
value: string;
|
|
262
|
+
type: string;
|
|
263
|
+
}[];
|
|
264
|
+
};
|
|
265
|
+
meta: {
|
|
266
|
+
code: number;
|
|
267
|
+
message: string;
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
declare const validatePublicKey: (publicKey: string) => void;
|
|
271
|
+
/**
|
|
272
|
+
* Fetches the current user from the identity host's OIDC `userinfo` endpoint.
|
|
273
|
+
* The identity host is taken from the validated public key payload (which the
|
|
274
|
+
* shared parser already constrains to the trusted-host allowlist), so the
|
|
275
|
+
* bearer token is never sent to an attacker-controlled origin.
|
|
276
|
+
*/
|
|
277
|
+
declare const fetchUserData: (publicKey: string, token: string) => Promise<IFetchUserData | null>;
|
|
278
|
+
|
|
279
|
+
export { AuthdogContext, type AuthdogContextValue, AuthdogProvider, type AuthdogProviderProps, type AuthdogStorage, type BuildAuthorizeUrlOptions, type IFetchUserData, type SecureStoreLike, type UseAuthzOptions, buildAuthorizeUrl, createSecureStoreAdapter, fetchUserData, getPublicKeyPayload, getTokenFromUri, inMemoryStorage, useAuthz, useRedirectHandler, useSession, useSignIn, useSignOut, useSignUp, useUser, validatePublicKey };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import * as react from 'react';
|
|
2
|
+
import { ReactNode } from 'react';
|
|
3
|
+
import { PublicKeyPayload } from '@authdog/node-commons';
|
|
4
|
+
export { PublicKeyPayload } from '@authdog/node-commons';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Pluggable, asynchronous token storage.
|
|
8
|
+
*
|
|
9
|
+
* Unlike the browser SDKs (which can read `localStorage` synchronously), mobile
|
|
10
|
+
* secure storage is always asynchronous. The SDK therefore talks to storage
|
|
11
|
+
* exclusively through this async interface so any backing store can be used:
|
|
12
|
+
* `expo-secure-store`, `@react-native-async-storage/async-storage`, the
|
|
13
|
+
* Keychain/Keystore, etc.
|
|
14
|
+
*
|
|
15
|
+
* For real apps you SHOULD use a hardware-backed secure store
|
|
16
|
+
* (`expo-secure-store`) so the session token is encrypted at rest rather than
|
|
17
|
+
* sitting in plain AsyncStorage.
|
|
18
|
+
*/
|
|
19
|
+
interface AuthdogStorage {
|
|
20
|
+
getItem: (key: string) => Promise<string | null>;
|
|
21
|
+
setItem: (key: string, value: string) => Promise<void>;
|
|
22
|
+
removeItem: (key: string) => Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Minimal in-memory storage. Intended as a safe default and for tests — the
|
|
26
|
+
* token does NOT survive an app restart and is NOT encrypted at rest. Provide
|
|
27
|
+
* a real `AuthdogStorage` (e.g. via {@link createSecureStoreAdapter}) in
|
|
28
|
+
* production.
|
|
29
|
+
*/
|
|
30
|
+
declare const inMemoryStorage: () => AuthdogStorage;
|
|
31
|
+
/**
|
|
32
|
+
* The subset of the `expo-secure-store` module the adapter relies on. Declared
|
|
33
|
+
* structurally so this package never has to depend on Expo directly.
|
|
34
|
+
*/
|
|
35
|
+
interface SecureStoreLike {
|
|
36
|
+
getItemAsync: (key: string) => Promise<string | null>;
|
|
37
|
+
setItemAsync: (key: string, value: string) => Promise<void>;
|
|
38
|
+
deleteItemAsync: (key: string) => Promise<void>;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Adapts the `expo-secure-store` module to {@link AuthdogStorage}.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* import * as SecureStore from "expo-secure-store";
|
|
45
|
+
* import { createSecureStoreAdapter } from "@authdog/react-native";
|
|
46
|
+
*
|
|
47
|
+
* const storage = createSecureStoreAdapter(SecureStore);
|
|
48
|
+
*/
|
|
49
|
+
declare const createSecureStoreAdapter: (secureStore: SecureStoreLike) => AuthdogStorage;
|
|
50
|
+
|
|
51
|
+
interface AuthdogContextValue {
|
|
52
|
+
/** The Authdog public key (`pk_…`) the app was configured with. */
|
|
53
|
+
readonly publicKey: string;
|
|
54
|
+
/** `true` while the persisted token is being loaded from storage on mount. */
|
|
55
|
+
readonly isLoading: boolean;
|
|
56
|
+
/** The current session token, or `null` when signed out. */
|
|
57
|
+
readonly token: string | null;
|
|
58
|
+
/** Async-aware token storage backing this provider. */
|
|
59
|
+
readonly storage: AuthdogStorage;
|
|
60
|
+
/** The storage key the session token is persisted under. */
|
|
61
|
+
readonly storageKey: string;
|
|
62
|
+
/**
|
|
63
|
+
* Persists (or clears, when passed `null`) the token in storage and updates
|
|
64
|
+
* the in-memory state. The returned promise resolves once storage settles.
|
|
65
|
+
*/
|
|
66
|
+
setToken: (token: string | null) => Promise<void>;
|
|
67
|
+
}
|
|
68
|
+
declare const AuthdogContext: react.Context<AuthdogContextValue | null>;
|
|
69
|
+
interface AuthdogProviderProps {
|
|
70
|
+
/** Your Authdog public key (`pk_…`). */
|
|
71
|
+
publicKey: string;
|
|
72
|
+
/**
|
|
73
|
+
* Token storage. Defaults to an in-memory store (token is lost on restart).
|
|
74
|
+
* Provide a secure store in production — see `createSecureStoreAdapter`.
|
|
75
|
+
*/
|
|
76
|
+
storage?: AuthdogStorage;
|
|
77
|
+
children?: ReactNode;
|
|
78
|
+
}
|
|
79
|
+
declare const AuthdogProvider: ({ publicKey, storage, children, }: AuthdogProviderProps) => react.JSX.Element;
|
|
80
|
+
|
|
81
|
+
declare const useSession: () => {
|
|
82
|
+
session: {
|
|
83
|
+
token: string | null;
|
|
84
|
+
isAuthenticated: boolean;
|
|
85
|
+
};
|
|
86
|
+
isLoading: boolean;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
declare const useUser: () => {
|
|
90
|
+
user: unknown;
|
|
91
|
+
isLoading: boolean;
|
|
92
|
+
error: Error | null;
|
|
93
|
+
isAuthenticated: boolean;
|
|
94
|
+
fetchUser: () => Promise<{
|
|
95
|
+
id: string;
|
|
96
|
+
environmentId: string;
|
|
97
|
+
externalId: string;
|
|
98
|
+
userName: string;
|
|
99
|
+
displayName: string;
|
|
100
|
+
nickName: string;
|
|
101
|
+
profileUrl: string;
|
|
102
|
+
title: string;
|
|
103
|
+
userType: string;
|
|
104
|
+
preferredLanguage: string | null;
|
|
105
|
+
locale: string | null;
|
|
106
|
+
timezone: string | null;
|
|
107
|
+
active: boolean;
|
|
108
|
+
provider: string;
|
|
109
|
+
lastLogin: string;
|
|
110
|
+
createdAt: string;
|
|
111
|
+
updatedAt: string;
|
|
112
|
+
names: {
|
|
113
|
+
id: string;
|
|
114
|
+
userId: string;
|
|
115
|
+
formatted: string | null;
|
|
116
|
+
familyName: string;
|
|
117
|
+
givenName: string;
|
|
118
|
+
middleName: string | null;
|
|
119
|
+
honorificPrefix: string | null;
|
|
120
|
+
honorificSuffix: string | null;
|
|
121
|
+
createdAt: string;
|
|
122
|
+
updatedAt: string;
|
|
123
|
+
};
|
|
124
|
+
addresses: [];
|
|
125
|
+
emails: {
|
|
126
|
+
value: string;
|
|
127
|
+
primary: boolean;
|
|
128
|
+
type: string;
|
|
129
|
+
}[];
|
|
130
|
+
phoneNumbers: [];
|
|
131
|
+
ims: [];
|
|
132
|
+
photos: {
|
|
133
|
+
value: string;
|
|
134
|
+
type: string;
|
|
135
|
+
}[];
|
|
136
|
+
} | null>;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
declare const useSignIn: () => {
|
|
140
|
+
signIn: (redirectUrl: string) => Promise<void>;
|
|
141
|
+
isLoading: boolean;
|
|
142
|
+
error: Error | null;
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
declare const useSignUp: () => {
|
|
146
|
+
signUp: (redirectUrl: string) => Promise<void>;
|
|
147
|
+
isLoading: boolean;
|
|
148
|
+
error: Error | null;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
declare const useSignOut: () => {
|
|
152
|
+
signOut: () => Promise<void>;
|
|
153
|
+
isLoading: boolean;
|
|
154
|
+
error: Error | null;
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
declare const useRedirectHandler: () => {
|
|
158
|
+
handleRedirect: (url: string) => Promise<string | null>;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* ⚠️ PRESENTATIONAL ONLY — NOT A SECURITY BOUNDARY.
|
|
163
|
+
*
|
|
164
|
+
* This hook fetches a permission list to drive UI affordances (showing or
|
|
165
|
+
* hiding buttons, tabs, screens, etc.). It runs entirely on the device and is
|
|
166
|
+
* therefore trivially bypassable by anyone who controls the client. It MUST
|
|
167
|
+
* NOT be used to gate access to data or actions.
|
|
168
|
+
*
|
|
169
|
+
* Every protected operation MUST be independently enforced server-side.
|
|
170
|
+
*/
|
|
171
|
+
interface UseAuthzOptions {
|
|
172
|
+
/**
|
|
173
|
+
* Absolute URL of the endpoint that returns the current user's permissions.
|
|
174
|
+
* Defaults to "/api/permissions" — note that on native you almost always
|
|
175
|
+
* need a fully-qualified URL. This endpoint is informational only;
|
|
176
|
+
* authorization must still be enforced on every protected server endpoint.
|
|
177
|
+
*/
|
|
178
|
+
permissionsUrl?: string;
|
|
179
|
+
}
|
|
180
|
+
declare const useAuthz: (options?: UseAuthzOptions) => {
|
|
181
|
+
permissions: string[];
|
|
182
|
+
isLoading: boolean;
|
|
183
|
+
error: Error | null;
|
|
184
|
+
fetchPermissions: () => Promise<string[]>;
|
|
185
|
+
hasPermission: (permission: string) => boolean;
|
|
186
|
+
hasAnyPermission: (list: string[]) => boolean;
|
|
187
|
+
hasAllPermissions: (list: string[]) => boolean;
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
interface BuildAuthorizeUrlOptions {
|
|
191
|
+
/**
|
|
192
|
+
* The app's deep-link/callback URL the identity server redirects back to,
|
|
193
|
+
* e.g. `myapp://callback`. This is registered as the OIDC `redirect_uri`.
|
|
194
|
+
*/
|
|
195
|
+
redirectUrl: string;
|
|
196
|
+
/** When `true`, hints the hosted UI to show the sign-up flow. */
|
|
197
|
+
signup?: boolean;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Builds the hosted OIDC authorize URL for the given public key.
|
|
201
|
+
*
|
|
202
|
+
* The identity host comes from the validated public key payload (constrained to
|
|
203
|
+
* the trusted-host allowlist by the shared parser), so a crafted key cannot
|
|
204
|
+
* point the login flow at an attacker-controlled origin.
|
|
205
|
+
*/
|
|
206
|
+
declare const buildAuthorizeUrl: (publicKey: string, { redirectUrl, signup }: BuildAuthorizeUrlOptions) => string;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Decodes and validates an Authdog public key. Delegates to the hardened
|
|
210
|
+
* shared parser in @authdog/node-commons, which validates the payload and
|
|
211
|
+
* enforces a trusted identity-host allowlist (SSRF / token-exfiltration
|
|
212
|
+
* protection) rather than blindly decoding base64/JSON.
|
|
213
|
+
*/
|
|
214
|
+
declare const getPublicKeyPayload: (publicKey: string) => PublicKeyPayload;
|
|
215
|
+
/**
|
|
216
|
+
* Extracts the `?token=` query parameter from a redirect/deep-link URL.
|
|
217
|
+
* Returns `null` when the URL is malformed or carries no token.
|
|
218
|
+
*/
|
|
219
|
+
declare const getTokenFromUri: (url: string) => string | null;
|
|
220
|
+
|
|
221
|
+
interface IFetchUserData {
|
|
222
|
+
user: {
|
|
223
|
+
id: string;
|
|
224
|
+
environmentId: string;
|
|
225
|
+
externalId: string;
|
|
226
|
+
userName: string;
|
|
227
|
+
displayName: string;
|
|
228
|
+
nickName: string;
|
|
229
|
+
profileUrl: string;
|
|
230
|
+
title: string;
|
|
231
|
+
userType: string;
|
|
232
|
+
preferredLanguage: string | null;
|
|
233
|
+
locale: string | null;
|
|
234
|
+
timezone: string | null;
|
|
235
|
+
active: boolean;
|
|
236
|
+
provider: string;
|
|
237
|
+
lastLogin: string;
|
|
238
|
+
createdAt: string;
|
|
239
|
+
updatedAt: string;
|
|
240
|
+
names: {
|
|
241
|
+
id: string;
|
|
242
|
+
userId: string;
|
|
243
|
+
formatted: string | null;
|
|
244
|
+
familyName: string;
|
|
245
|
+
givenName: string;
|
|
246
|
+
middleName: string | null;
|
|
247
|
+
honorificPrefix: string | null;
|
|
248
|
+
honorificSuffix: string | null;
|
|
249
|
+
createdAt: string;
|
|
250
|
+
updatedAt: string;
|
|
251
|
+
};
|
|
252
|
+
addresses: [];
|
|
253
|
+
emails: {
|
|
254
|
+
value: string;
|
|
255
|
+
primary: boolean;
|
|
256
|
+
type: string;
|
|
257
|
+
}[];
|
|
258
|
+
phoneNumbers: [];
|
|
259
|
+
ims: [];
|
|
260
|
+
photos: {
|
|
261
|
+
value: string;
|
|
262
|
+
type: string;
|
|
263
|
+
}[];
|
|
264
|
+
};
|
|
265
|
+
meta: {
|
|
266
|
+
code: number;
|
|
267
|
+
message: string;
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
declare const validatePublicKey: (publicKey: string) => void;
|
|
271
|
+
/**
|
|
272
|
+
* Fetches the current user from the identity host's OIDC `userinfo` endpoint.
|
|
273
|
+
* The identity host is taken from the validated public key payload (which the
|
|
274
|
+
* shared parser already constrains to the trusted-host allowlist), so the
|
|
275
|
+
* bearer token is never sent to an attacker-controlled origin.
|
|
276
|
+
*/
|
|
277
|
+
declare const fetchUserData: (publicKey: string, token: string) => Promise<IFetchUserData | null>;
|
|
278
|
+
|
|
279
|
+
export { AuthdogContext, type AuthdogContextValue, AuthdogProvider, type AuthdogProviderProps, type AuthdogStorage, type BuildAuthorizeUrlOptions, type IFetchUserData, type SecureStoreLike, type UseAuthzOptions, buildAuthorizeUrl, createSecureStoreAdapter, fetchUserData, getPublicKeyPayload, getTokenFromUri, inMemoryStorage, useAuthz, useRedirectHandler, useSession, useSignIn, useSignOut, useSignUp, useUser, validatePublicKey };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";var L=Object.defineProperty;var q=Object.getOwnPropertyDescriptor;var G=Object.getOwnPropertyNames;var Q=Object.prototype.hasOwnProperty;var X=(t,e)=>{for(var r in e)L(t,r,{get:e[r],enumerable:!0})},Y=(t,e,r,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let o of G(e))!Q.call(t,o)&&o!==r&&L(t,o,{get:()=>e[o],enumerable:!(n=q(e,o))||n.enumerable});return t};var tt=t=>Y(L({},"__esModule",{value:!0}),t);var rt={};X(rt,{AuthdogContext:()=>x,AuthdogProvider:()=>M,buildAuthorizeUrl:()=>y,createSecureStoreAdapter:()=>$,fetchUserData:()=>w,getPublicKeyPayload:()=>p,getTokenFromUri:()=>b,inMemoryStorage:()=>v,useAuthz:()=>D,useRedirectHandler:()=>O,useSession:()=>E,useSignIn:()=>K,useSignOut:()=>T,useSignUp:()=>R,useUser:()=>z,validatePublicKey:()=>A});module.exports=tt(rt);var H=require("@authdog/node-commons"),i=require("react");var F=require("@authdog/node-commons"),p=t=>(0,F.validateAndParsePublicKey)(t),_=/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/,b=t=>{try{return new URL(t).searchParams.get("token")}catch{return null}};var v=()=>{let t=new Map;return{getItem:async e=>t.get(e)??null,setItem:async(e,r)=>{t.set(e,r)},removeItem:async e=>{t.delete(e)}}},$=t=>({getItem:e=>t.getItemAsync(e),setItem:(e,r)=>t.setItemAsync(e,r),removeItem:e=>t.deleteItemAsync(e)});var V=require("react/jsx-runtime"),x=(0,i.createContext)(null),et=t=>{try{return(0,H.buildSessionKey)(p(t).environmentId)}catch{return"token"}},M=({publicKey:t,storage:e,children:r})=>{let[n,o]=(0,i.useState)(!0),[g,u]=(0,i.useState)(null),s=(0,i.useRef)(e??v());e&&s.current!==e&&(s.current=e);let l=(0,i.useMemo)(()=>et(t),[t]);(0,i.useEffect)(()=>{let d=!1;return(async()=>{try{let S=await s.current.getItem(l);!d&&S&&u(S)}catch{}finally{d||o(!1)}})(),()=>{d=!0}},[l]);let f=(0,i.useCallback)(async d=>{u(d),d===null?await s.current.removeItem(l):await s.current.setItem(l,d)},[l]),C=(0,i.useMemo)(()=>({publicKey:t,isLoading:n,token:g,storage:s.current,storageKey:l,setToken:f}),[t,n,g,l,f]);return(0,V.jsx)(x.Provider,{value:C,children:r})};var W=require("react");var B=require("react");var a=t=>{let e=(0,B.useContext)(x);if(!e)throw new Error(`${t} must be used within AuthdogProvider`);return e};var E=()=>{let t=a("useSession");return{session:(0,W.useMemo)(()=>({token:t.token,isAuthenticated:!!t.token}),[t.token]),isLoading:t.isLoading}};var h=require("react");var A=t=>{if(!t)throw new Error("Public key is not defined");if(!t.startsWith("pk_"))throw new Error("Invalid public key")},w=async(t,e)=>{A(t);let r=p(t),n=await fetch(`${r.identityHost}/oidc/${r.environmentId}/userinfo`,{headers:{authorization:`Bearer ${e}`}});if(!n.ok)throw new Error("Failed to fetch user info");return await n.json()};var z=()=>{let t=a("useUser"),[e,r]=(0,h.useState)(null),[n,o]=(0,h.useState)(!1),[g,u]=(0,h.useState)(null),s=(0,h.useCallback)(async()=>{if(!t.token)return null;o(!0),u(null);try{A(t.publicKey);let f=(await w(t.publicKey,t.token))?.user??null;return r(f),f}catch(l){return u(l),null}finally{o(!1)}},[t.publicKey,t.token]);return{user:e,isLoading:n,error:g,isAuthenticated:!!t.token&&!!e,fetchUser:s}};var P=require("react"),Z=require("react-native");var y=(t,{redirectUrl:e,signup:r})=>{let n=p(t),o=new URL(`${n.identityHost}/oidc/${n.environmentId}/authorize`);return o.searchParams.set("client_id",t),o.searchParams.set("response_type","code"),o.searchParams.set("scope","openid profile email"),o.searchParams.set("redirect_uri",e),r&&o.searchParams.set("prompt","signup"),o.toString()};var K=()=>{let t=a("useSignIn"),[e,r]=(0,P.useState)(!1),[n,o]=(0,P.useState)(null);return{signIn:(0,P.useCallback)(async u=>{r(!0),o(null);try{let s=y(t.publicKey,{redirectUrl:u});await Z.Linking.openURL(s)}catch(s){o(s)}finally{r(!1)}},[t.publicKey]),isLoading:e,error:n}};var k=require("react"),j=require("react-native");var R=()=>{let t=a("useSignUp"),[e,r]=(0,k.useState)(!1),[n,o]=(0,k.useState)(null);return{signUp:(0,k.useCallback)(async u=>{r(!0),o(null);try{let s=y(t.publicKey,{redirectUrl:u,signup:!0});await j.Linking.openURL(s)}catch(s){o(s)}finally{r(!1)}},[t.publicKey]),isLoading:e,error:n}};var U=require("react");var T=()=>{let t=a("useSignOut"),[e,r]=(0,U.useState)(!1),[n,o]=(0,U.useState)(null);return{signOut:(0,U.useCallback)(async()=>{r(!0),o(null);try{await t.setToken(null)}catch(u){o(u)}finally{r(!1)}},[t]),isLoading:e,error:n}};var J=require("react");var O=()=>{let t=a("useRedirectHandler");return{handleRedirect:(0,J.useCallback)(async r=>{let n=b(r);return!n||!_.test(n)?null:(await t.setToken(n),n)},[t])}};var m=require("react");var D=(t={})=>{let e=a("useAuthz"),[r,n]=(0,m.useState)([]),[o,g]=(0,m.useState)(!1),[u,s]=(0,m.useState)(null),l=t.permissionsUrl??"/api/permissions",f=(0,m.useCallback)(async()=>{if(!e.token)return[];g(!0),s(null);try{let c=await fetch(l,{headers:{Authorization:`Bearer ${e.token}`}});if(c.status===401)throw n([]),new Error("Unauthorized: authentication failed (401)");if(!c.ok)throw new Error(`Failed to fetch permissions (status ${c.status})`);let N=(await c.json()).permissions??[];return n(N),N}catch(c){return s(c),[]}finally{g(!1)}},[e.token,l]),C=(0,m.useCallback)(c=>r.includes(c),[r]),d=(0,m.useCallback)(c=>c.some(I=>r.includes(I)),[r]),S=(0,m.useCallback)(c=>c.every(I=>r.includes(I)),[r]);return{permissions:r,isLoading:o,error:u,fetchPermissions:f,hasPermission:C,hasAnyPermission:d,hasAllPermissions:S}};
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/provider.tsx","../src/commons.ts","../src/storage.ts","../src/hooks/use-session.ts","../src/hooks/use-authdog-context.ts","../src/hooks/use-user.ts","../src/session.ts","../src/hooks/use-signin.ts","../src/auth-url.ts","../src/hooks/use-signup.ts","../src/hooks/use-signout.ts","../src/hooks/use-redirect.ts","../src/hooks/use-authz.ts"],"sourcesContent":["// Provider + context\nexport {\n AuthdogProvider,\n AuthdogContext,\n type AuthdogProviderProps,\n type AuthdogContextValue,\n} from \"./provider\";\n\n// Hooks\nexport {\n useSession,\n useUser,\n useSignIn,\n useSignUp,\n useSignOut,\n useRedirectHandler,\n useAuthz,\n type UseAuthzOptions,\n} from \"./hooks\";\n\n// Storage\nexport {\n type AuthdogStorage,\n type SecureStoreLike,\n inMemoryStorage,\n createSecureStoreAdapter,\n} from \"./storage\";\n\n// Lower-level helpers\nexport { buildAuthorizeUrl, type BuildAuthorizeUrlOptions } from \"./auth-url\";\nexport { getPublicKeyPayload, getTokenFromUri, type PublicKeyPayload } from \"./commons\";\nexport { fetchUserData, validatePublicKey, type IFetchUserData } from \"./session\";\n","import { buildSessionKey } from \"@authdog/node-commons\";\nimport {\n createContext,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { getPublicKeyPayload } from \"./commons\";\nimport { type AuthdogStorage, inMemoryStorage } from \"./storage\";\n\nexport interface AuthdogContextValue {\n /** The Authdog public key (`pk_…`) the app was configured with. */\n readonly publicKey: string;\n /** `true` while the persisted token is being loaded from storage on mount. */\n readonly isLoading: boolean;\n /** The current session token, or `null` when signed out. */\n readonly token: string | null;\n /** Async-aware token storage backing this provider. */\n readonly storage: AuthdogStorage;\n /** The storage key the session token is persisted under. */\n readonly storageKey: string;\n /**\n * Persists (or clears, when passed `null`) the token in storage and updates\n * the in-memory state. The returned promise resolves once storage settles.\n */\n setToken: (token: string | null) => Promise<void>;\n}\n\nexport const AuthdogContext = createContext<AuthdogContextValue | null>(null);\n\n/**\n * Resolves the storage key for a given public key. Falls back to a stable\n * default if the key cannot be parsed, so a misconfigured key never throws\n * during render.\n */\nconst resolveStorageKey = (publicKey: string): string => {\n try {\n return buildSessionKey(getPublicKeyPayload(publicKey).environmentId);\n } catch {\n return \"token\";\n }\n};\n\nexport interface AuthdogProviderProps {\n /** Your Authdog public key (`pk_…`). */\n publicKey: string;\n /**\n * Token storage. Defaults to an in-memory store (token is lost on restart).\n * Provide a secure store in production — see `createSecureStoreAdapter`.\n */\n storage?: AuthdogStorage;\n children?: ReactNode;\n}\n\nexport const AuthdogProvider = ({\n publicKey,\n storage,\n children,\n}: AuthdogProviderProps) => {\n const [isLoading, setIsLoading] = useState(true);\n const [token, setTokenState] = useState<string | null>(null);\n\n // Keep storage / key stable across renders so effects don't re-run and the\n // returned context identity only changes when state actually changes.\n const storageRef = useRef<AuthdogStorage>(storage ?? inMemoryStorage());\n if (storage && storageRef.current !== storage) {\n storageRef.current = storage;\n }\n const storageKey = useMemo(() => resolveStorageKey(publicKey), [publicKey]);\n\n useEffect(() => {\n let cancelled = false;\n\n (async () => {\n try {\n const existing = await storageRef.current.getItem(storageKey);\n if (!cancelled && existing) {\n setTokenState(existing);\n }\n } catch {\n // A storage read failure is non-fatal: treat it as \"signed out\".\n } finally {\n if (!cancelled) {\n setIsLoading(false);\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [storageKey]);\n\n const setToken = useCallback(\n async (next: string | null) => {\n setTokenState(next);\n if (next === null) {\n await storageRef.current.removeItem(storageKey);\n } else {\n await storageRef.current.setItem(storageKey, next);\n }\n },\n [storageKey],\n );\n\n const value = useMemo<AuthdogContextValue>(\n () => ({\n publicKey,\n isLoading,\n token,\n storage: storageRef.current,\n storageKey,\n setToken,\n }),\n [publicKey, isLoading, token, storageKey, setToken],\n );\n\n return (\n <AuthdogContext.Provider value={value}>{children}</AuthdogContext.Provider>\n );\n};\n","import {\n validateAndParsePublicKey,\n type PublicKeyPayload,\n} from \"@authdog/node-commons\";\n\nexport type { PublicKeyPayload };\n\n/**\n * Decodes and validates an Authdog public key. Delegates to the hardened\n * shared parser in @authdog/node-commons, which validates the payload and\n * enforces a trusted identity-host allowlist (SSRF / token-exfiltration\n * protection) rather than blindly decoding base64/JSON.\n */\nexport const getPublicKeyPayload = (publicKey: string): PublicKeyPayload => {\n return validateAndParsePublicKey(publicKey);\n};\n\n/** JWT shape: three base64url segments separated by dots. */\nexport const JWT_PATTERN =\n /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/;\n\n/**\n * Extracts the `?token=` query parameter from a redirect/deep-link URL.\n * Returns `null` when the URL is malformed or carries no token.\n */\nexport const getTokenFromUri = (url: string): string | null => {\n try {\n return new URL(url).searchParams.get(\"token\");\n } catch {\n return null;\n }\n};\n","/**\n * Pluggable, asynchronous token storage.\n *\n * Unlike the browser SDKs (which can read `localStorage` synchronously), mobile\n * secure storage is always asynchronous. The SDK therefore talks to storage\n * exclusively through this async interface so any backing store can be used:\n * `expo-secure-store`, `@react-native-async-storage/async-storage`, the\n * Keychain/Keystore, etc.\n *\n * For real apps you SHOULD use a hardware-backed secure store\n * (`expo-secure-store`) so the session token is encrypted at rest rather than\n * sitting in plain AsyncStorage.\n */\nexport interface AuthdogStorage {\n getItem: (key: string) => Promise<string | null>;\n setItem: (key: string, value: string) => Promise<void>;\n removeItem: (key: string) => Promise<void>;\n}\n\n/**\n * Minimal in-memory storage. Intended as a safe default and for tests — the\n * token does NOT survive an app restart and is NOT encrypted at rest. Provide\n * a real `AuthdogStorage` (e.g. via {@link createSecureStoreAdapter}) in\n * production.\n */\nexport const inMemoryStorage = (): AuthdogStorage => {\n const store = new Map<string, string>();\n return {\n getItem: async (key) => store.get(key) ?? null,\n setItem: async (key, value) => {\n store.set(key, value);\n },\n removeItem: async (key) => {\n store.delete(key);\n },\n };\n};\n\n/**\n * The subset of the `expo-secure-store` module the adapter relies on. Declared\n * structurally so this package never has to depend on Expo directly.\n */\nexport interface SecureStoreLike {\n getItemAsync: (key: string) => Promise<string | null>;\n setItemAsync: (key: string, value: string) => Promise<void>;\n deleteItemAsync: (key: string) => Promise<void>;\n}\n\n/**\n * Adapts the `expo-secure-store` module to {@link AuthdogStorage}.\n *\n * @example\n * import * as SecureStore from \"expo-secure-store\";\n * import { createSecureStoreAdapter } from \"@authdog/react-native\";\n *\n * const storage = createSecureStoreAdapter(SecureStore);\n */\nexport const createSecureStoreAdapter = (\n secureStore: SecureStoreLike,\n): AuthdogStorage => ({\n getItem: (key) => secureStore.getItemAsync(key),\n setItem: (key, value) => secureStore.setItemAsync(key, value),\n removeItem: (key) => secureStore.deleteItemAsync(key),\n});\n","import { useMemo } from \"react\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useSession = () => {\n const context = useAuthdogContext(\"useSession\");\n\n const session = useMemo(\n () => ({\n token: context.token,\n isAuthenticated: !!context.token,\n }),\n [context.token],\n );\n\n return {\n session,\n isLoading: context.isLoading,\n };\n};\n","import { useContext } from \"react\";\nimport { AuthdogContext, type AuthdogContextValue } from \"../provider\";\n\n/**\n * Internal helper: returns the Authdog context or throws a clear error when a\n * hook is used outside `<AuthdogProvider>`. The `hookName` is interpolated so\n * the message points at the offending hook.\n */\nexport const useAuthdogContext = (hookName: string): AuthdogContextValue => {\n const context = useContext(AuthdogContext);\n if (!context) {\n throw new Error(`${hookName} must be used within AuthdogProvider`);\n }\n return context;\n};\n","import { useCallback, useState } from \"react\";\nimport { fetchUserData, validatePublicKey } from \"../session\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useUser = () => {\n const context = useAuthdogContext(\"useUser\");\n const [user, setUser] = useState<unknown>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // The public key is supplied to the provider, so callers don't need to pass\n // it again — it is read from context.\n const fetchUser = useCallback(async () => {\n if (!context.token) {\n return null;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n validatePublicKey(context.publicKey);\n const userData = await fetchUserData(context.publicKey, context.token);\n const nextUser = userData?.user ?? null;\n setUser(nextUser);\n return nextUser;\n } catch (err) {\n setError(err as Error);\n return null;\n } finally {\n setIsLoading(false);\n }\n }, [context.publicKey, context.token]);\n\n return {\n user,\n isLoading,\n error,\n isAuthenticated: !!context.token && !!user,\n fetchUser,\n };\n};\n","import { getPublicKeyPayload } from \"./commons\";\n\ninterface IFetchUserData {\n user: {\n id: string;\n environmentId: string;\n externalId: string;\n userName: string;\n displayName: string;\n nickName: string;\n profileUrl: string;\n title: string;\n userType: string;\n preferredLanguage: string | null;\n locale: string | null;\n timezone: string | null;\n active: boolean;\n provider: string;\n lastLogin: string;\n createdAt: string;\n updatedAt: string;\n names: {\n id: string;\n userId: string;\n formatted: string | null;\n familyName: string;\n givenName: string;\n middleName: string | null;\n honorificPrefix: string | null;\n honorificSuffix: string | null;\n createdAt: string;\n updatedAt: string;\n };\n addresses: [];\n emails: {\n value: string;\n primary: boolean;\n type: string;\n }[];\n phoneNumbers: [];\n ims: [];\n photos: {\n value: string;\n type: string;\n }[];\n };\n meta: {\n code: number;\n message: string;\n };\n}\n\nexport const validatePublicKey = (publicKey: string) => {\n if (!publicKey) {\n throw new Error(\"Public key is not defined\");\n }\n\n if (!publicKey.startsWith(\"pk_\")) {\n throw new Error(\"Invalid public key\");\n }\n};\n\n/**\n * Fetches the current user from the identity host's OIDC `userinfo` endpoint.\n * The identity host is taken from the validated public key payload (which the\n * shared parser already constrains to the trusted-host allowlist), so the\n * bearer token is never sent to an attacker-controlled origin.\n */\nexport const fetchUserData = async (\n publicKey: string,\n token: string,\n): Promise<IFetchUserData | null> => {\n validatePublicKey(publicKey);\n const publicKeyObj = getPublicKeyPayload(publicKey);\n const userData = await fetch(\n `${publicKeyObj.identityHost}/oidc/${publicKeyObj.environmentId}/userinfo`,\n {\n headers: {\n authorization: `Bearer ${token}`,\n },\n },\n );\n\n if (!userData.ok) {\n throw new Error(\"Failed to fetch user info\");\n }\n\n return (await userData.json()) as IFetchUserData;\n};\n\nexport type { IFetchUserData };\n","import { useCallback, useState } from \"react\";\nimport { Linking } from \"react-native\";\nimport { buildAuthorizeUrl } from \"../auth-url\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useSignIn = () => {\n const context = useAuthdogContext(\"useSignIn\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n /**\n * Opens the hosted sign-in flow in the system browser. `redirectUrl` MUST be\n * a deep link registered by your app (e.g. `myapp://callback`) so the\n * identity server can return the user — pass it to `handleRedirect` to\n * complete sign-in.\n */\n const signIn = useCallback(\n async (redirectUrl: string) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const authUrl = buildAuthorizeUrl(context.publicKey, { redirectUrl });\n await Linking.openURL(authUrl);\n } catch (err) {\n setError(err as Error);\n } finally {\n setIsLoading(false);\n }\n },\n [context.publicKey],\n );\n\n return { signIn, isLoading, error };\n};\n","import { getPublicKeyPayload } from \"./commons\";\n\nexport interface BuildAuthorizeUrlOptions {\n /**\n * The app's deep-link/callback URL the identity server redirects back to,\n * e.g. `myapp://callback`. This is registered as the OIDC `redirect_uri`.\n */\n redirectUrl: string;\n /** When `true`, hints the hosted UI to show the sign-up flow. */\n signup?: boolean;\n}\n\n/**\n * Builds the hosted OIDC authorize URL for the given public key.\n *\n * The identity host comes from the validated public key payload (constrained to\n * the trusted-host allowlist by the shared parser), so a crafted key cannot\n * point the login flow at an attacker-controlled origin.\n */\nexport const buildAuthorizeUrl = (\n publicKey: string,\n { redirectUrl, signup }: BuildAuthorizeUrlOptions,\n): string => {\n const payload = getPublicKeyPayload(publicKey);\n\n const authUrl = new URL(\n `${payload.identityHost}/oidc/${payload.environmentId}/authorize`,\n );\n authUrl.searchParams.set(\"client_id\", publicKey);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"scope\", \"openid profile email\");\n authUrl.searchParams.set(\"redirect_uri\", redirectUrl);\n if (signup) {\n authUrl.searchParams.set(\"prompt\", \"signup\");\n }\n\n return authUrl.toString();\n};\n","import { useCallback, useState } from \"react\";\nimport { Linking } from \"react-native\";\nimport { buildAuthorizeUrl } from \"../auth-url\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useSignUp = () => {\n const context = useAuthdogContext(\"useSignUp\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n /**\n * Opens the hosted sign-up flow (`prompt=signup`) in the system browser.\n * `redirectUrl` MUST be a deep link registered by your app.\n */\n const signUp = useCallback(\n async (redirectUrl: string) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const authUrl = buildAuthorizeUrl(context.publicKey, {\n redirectUrl,\n signup: true,\n });\n await Linking.openURL(authUrl);\n } catch (err) {\n setError(err as Error);\n } finally {\n setIsLoading(false);\n }\n },\n [context.publicKey],\n );\n\n return { signUp, isLoading, error };\n};\n","import { useCallback, useState } from \"react\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useSignOut = () => {\n const context = useAuthdogContext(\"useSignOut\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n /**\n * Clears the session token from both in-memory state and the backing\n * storage. There is no browser to redirect on native, so callers are\n * responsible for any post-sign-out navigation.\n */\n const signOut = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n\n try {\n await context.setToken(null);\n } catch (err) {\n setError(err as Error);\n } finally {\n setIsLoading(false);\n }\n }, [context]);\n\n return { signOut, isLoading, error };\n};\n","import { useCallback } from \"react\";\nimport { JWT_PATTERN, getTokenFromUri } from \"../commons\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useRedirectHandler = () => {\n const context = useAuthdogContext(\"useRedirectHandler\");\n\n /**\n * Completes sign-in from a returned deep link (e.g. the URL delivered to\n * `Linking.addEventListener(\"url\", …)` or `Linking.getInitialURL()`).\n *\n * The `?token=` value is validated against the JWT shape BEFORE it is\n * persisted, so an attacker who can craft a deep link cannot get arbitrary\n * data written into secure storage. Returns the token on success, otherwise\n * `null`.\n */\n const handleRedirect = useCallback(\n async (url: string): Promise<string | null> => {\n const token = getTokenFromUri(url);\n\n // Only persist values that look like a JWT.\n if (!token || !JWT_PATTERN.test(token)) {\n return null;\n }\n\n await context.setToken(token);\n return token;\n },\n [context],\n );\n\n return { handleRedirect };\n};\n","import { useCallback, useState } from \"react\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\n/**\n * ⚠️ PRESENTATIONAL ONLY — NOT A SECURITY BOUNDARY.\n *\n * This hook fetches a permission list to drive UI affordances (showing or\n * hiding buttons, tabs, screens, etc.). It runs entirely on the device and is\n * therefore trivially bypassable by anyone who controls the client. It MUST\n * NOT be used to gate access to data or actions.\n *\n * Every protected operation MUST be independently enforced server-side.\n */\n\nexport interface UseAuthzOptions {\n /**\n * Absolute URL of the endpoint that returns the current user's permissions.\n * Defaults to \"/api/permissions\" — note that on native you almost always\n * need a fully-qualified URL. This endpoint is informational only;\n * authorization must still be enforced on every protected server endpoint.\n */\n permissionsUrl?: string;\n}\n\nexport const useAuthz = (options: UseAuthzOptions = {}) => {\n const context = useAuthdogContext(\"useAuthz\");\n const [permissions, setPermissions] = useState<string[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const permissionsUrl = options.permissionsUrl ?? \"/api/permissions\";\n\n const fetchPermissions = useCallback(async () => {\n if (!context.token) {\n return [];\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const response = await fetch(permissionsUrl, {\n headers: {\n Authorization: `Bearer ${context.token}`,\n },\n });\n\n // Distinguish an authentication failure from an empty permission list.\n // A 401 means the session is invalid/expired and should surface as an\n // error rather than being silently coerced into \"no permissions\".\n if (response.status === 401) {\n setPermissions([]);\n throw new Error(\"Unauthorized: authentication failed (401)\");\n }\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch permissions (status ${response.status})`,\n );\n }\n\n const data = (await response.json()) as { permissions?: string[] };\n const next: string[] = data.permissions ?? [];\n setPermissions(next);\n return next;\n } catch (err) {\n setError(err as Error);\n return [];\n } finally {\n setIsLoading(false);\n }\n }, [context.token, permissionsUrl]);\n\n /**\n * ⚠️ PRESENTATIONAL ONLY. Returns whether the locally-cached permission list\n * contains `permission`. For UI hints only and is bypassable; never rely on\n * it as an access-control check. Enforce permissions server-side.\n */\n const hasPermission = useCallback(\n (permission: string) => permissions.includes(permission),\n [permissions],\n );\n\n const hasAnyPermission = useCallback(\n (list: string[]) => list.some((p) => permissions.includes(p)),\n [permissions],\n );\n\n const hasAllPermissions = useCallback(\n (list: string[]) => list.every((p) => permissions.includes(p)),\n [permissions],\n );\n\n return {\n permissions,\n isLoading,\n error,\n fetchPermissions,\n hasPermission,\n hasAnyPermission,\n hasAllPermissions,\n };\n};\n"],"mappings":"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","names":["index_exports","__export","AuthdogContext","AuthdogProvider","buildAuthorizeUrl","createSecureStoreAdapter","fetchUserData","getPublicKeyPayload","getTokenFromUri","inMemoryStorage","useAuthz","useRedirectHandler","useSession","useSignIn","useSignOut","useSignUp","useUser","validatePublicKey","__toCommonJS","import_node_commons","import_react","import_node_commons","getPublicKeyPayload","publicKey","JWT_PATTERN","getTokenFromUri","url","inMemoryStorage","store","key","value","createSecureStoreAdapter","secureStore","import_jsx_runtime","AuthdogContext","resolveStorageKey","publicKey","getPublicKeyPayload","AuthdogProvider","storage","children","isLoading","setIsLoading","token","setTokenState","storageRef","inMemoryStorage","storageKey","cancelled","existing","setToken","next","value","import_react","import_react","useAuthdogContext","hookName","context","AuthdogContext","useSession","context","useAuthdogContext","import_react","validatePublicKey","publicKey","fetchUserData","token","publicKeyObj","getPublicKeyPayload","userData","useUser","context","useAuthdogContext","user","setUser","isLoading","setIsLoading","error","setError","fetchUser","validatePublicKey","nextUser","fetchUserData","err","import_react","import_react_native","buildAuthorizeUrl","publicKey","redirectUrl","signup","payload","getPublicKeyPayload","authUrl","useSignIn","context","useAuthdogContext","isLoading","setIsLoading","error","setError","redirectUrl","authUrl","buildAuthorizeUrl","err","import_react","import_react_native","useSignUp","context","useAuthdogContext","isLoading","setIsLoading","error","setError","redirectUrl","authUrl","buildAuthorizeUrl","err","import_react","useSignOut","context","useAuthdogContext","isLoading","setIsLoading","error","setError","err","import_react","useRedirectHandler","context","useAuthdogContext","url","token","getTokenFromUri","JWT_PATTERN","import_react","useAuthz","options","context","useAuthdogContext","permissions","setPermissions","isLoading","setIsLoading","error","setError","permissionsUrl","fetchPermissions","response","next","err","hasPermission","permission","hasAnyPermission","list","p","hasAllPermissions"]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{buildSessionKey as M}from"@authdog/node-commons";import{createContext as V,useCallback as B,useEffect as W,useMemo as C,useRef as Z,useState as L}from"react";import{validateAndParsePublicKey as $}from"@authdog/node-commons";var m=t=>$(t),w=/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/,k=t=>{try{return new URL(t).searchParams.get("token")}catch{return null}};var U=()=>{let t=new Map;return{getItem:async e=>t.get(e)??null,setItem:async(e,r)=>{t.set(e,r)},removeItem:async e=>{t.delete(e)}}},H=t=>({getItem:e=>t.getItemAsync(e),setItem:(e,r)=>t.setItemAsync(e,r),removeItem:e=>t.deleteItemAsync(e)});import{jsx as q}from"react/jsx-runtime";var y=V(null),j=t=>{try{return M(m(t).environmentId)}catch{return"token"}},J=({publicKey:t,storage:e,children:r})=>{let[o,n]=L(!0),[c,a]=L(null),s=Z(e??U());e&&s.current!==e&&(s.current=e);let u=C(()=>j(t),[t]);W(()=>{let g=!1;return(async()=>{try{let f=await s.current.getItem(u);!g&&f&&a(f)}catch{}finally{g||n(!1)}})(),()=>{g=!0}},[u]);let d=B(async g=>{a(g),g===null?await s.current.removeItem(u):await s.current.setItem(u,g)},[u]),P=C(()=>({publicKey:t,isLoading:o,token:c,storage:s.current,storageKey:u,setToken:d}),[t,o,c,u,d]);return q(y.Provider,{value:P,children:r})};import{useMemo as Q}from"react";import{useContext as G}from"react";var i=t=>{let e=G(y);if(!e)throw new Error(`${t} must be used within AuthdogProvider`);return e};var E=()=>{let t=i("useSession");return{session:Q(()=>({token:t.token,isAuthenticated:!!t.token}),[t.token]),isLoading:t.isLoading}};import{useCallback as X,useState as I}from"react";var x=t=>{if(!t)throw new Error("Public key is not defined");if(!t.startsWith("pk_"))throw new Error("Invalid public key")},S=async(t,e)=>{x(t);let r=m(t),o=await fetch(`${r.identityHost}/oidc/${r.environmentId}/userinfo`,{headers:{authorization:`Bearer ${e}`}});if(!o.ok)throw new Error("Failed to fetch user info");return await o.json()};var z=()=>{let t=i("useUser"),[e,r]=I(null),[o,n]=I(!1),[c,a]=I(null),s=X(async()=>{if(!t.token)return null;n(!0),a(null);try{x(t.publicKey);let d=(await S(t.publicKey,t.token))?.user??null;return r(d),d}catch(u){return a(u),null}finally{n(!1)}},[t.publicKey,t.token]);return{user:e,isLoading:o,error:c,isAuthenticated:!!t.token&&!!e,fetchUser:s}};import{useCallback as Y,useState as K}from"react";import{Linking as tt}from"react-native";var p=(t,{redirectUrl:e,signup:r})=>{let o=m(t),n=new URL(`${o.identityHost}/oidc/${o.environmentId}/authorize`);return n.searchParams.set("client_id",t),n.searchParams.set("response_type","code"),n.searchParams.set("scope","openid profile email"),n.searchParams.set("redirect_uri",e),r&&n.searchParams.set("prompt","signup"),n.toString()};var R=()=>{let t=i("useSignIn"),[e,r]=K(!1),[o,n]=K(null);return{signIn:Y(async a=>{r(!0),n(null);try{let s=p(t.publicKey,{redirectUrl:a});await tt.openURL(s)}catch(s){n(s)}finally{r(!1)}},[t.publicKey]),isLoading:e,error:o}};import{useCallback as et,useState as T}from"react";import{Linking as rt}from"react-native";var O=()=>{let t=i("useSignUp"),[e,r]=T(!1),[o,n]=T(null);return{signUp:et(async a=>{r(!0),n(null);try{let s=p(t.publicKey,{redirectUrl:a,signup:!0});await rt.openURL(s)}catch(s){n(s)}finally{r(!1)}},[t.publicKey]),isLoading:e,error:o}};import{useCallback as ot,useState as D}from"react";var N=()=>{let t=i("useSignOut"),[e,r]=D(!1),[o,n]=D(null);return{signOut:ot(async()=>{r(!0),n(null);try{await t.setToken(null)}catch(a){n(a)}finally{r(!1)}},[t]),isLoading:e,error:o}};import{useCallback as nt}from"react";var F=()=>{let t=i("useRedirectHandler");return{handleRedirect:nt(async r=>{let o=k(r);return!o||!w.test(o)?null:(await t.setToken(o),o)},[t])}};import{useCallback as A,useState as b}from"react";var _=(t={})=>{let e=i("useAuthz"),[r,o]=b([]),[n,c]=b(!1),[a,s]=b(null),u=t.permissionsUrl??"/api/permissions",d=A(async()=>{if(!e.token)return[];c(!0),s(null);try{let l=await fetch(u,{headers:{Authorization:`Bearer ${e.token}`}});if(l.status===401)throw o([]),new Error("Unauthorized: authentication failed (401)");if(!l.ok)throw new Error(`Failed to fetch permissions (status ${l.status})`);let v=(await l.json()).permissions??[];return o(v),v}catch(l){return s(l),[]}finally{c(!1)}},[e.token,u]),P=A(l=>r.includes(l),[r]),g=A(l=>l.some(h=>r.includes(h)),[r]),f=A(l=>l.every(h=>r.includes(h)),[r]);return{permissions:r,isLoading:n,error:a,fetchPermissions:d,hasPermission:P,hasAnyPermission:g,hasAllPermissions:f}};export{y as AuthdogContext,J as AuthdogProvider,p as buildAuthorizeUrl,H as createSecureStoreAdapter,S as fetchUserData,m as getPublicKeyPayload,k as getTokenFromUri,U as inMemoryStorage,_ as useAuthz,F as useRedirectHandler,E as useSession,R as useSignIn,N as useSignOut,O as useSignUp,z as useUser,x as validatePublicKey};
|
|
2
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/provider.tsx","../src/commons.ts","../src/storage.ts","../src/hooks/use-session.ts","../src/hooks/use-authdog-context.ts","../src/hooks/use-user.ts","../src/session.ts","../src/hooks/use-signin.ts","../src/auth-url.ts","../src/hooks/use-signup.ts","../src/hooks/use-signout.ts","../src/hooks/use-redirect.ts","../src/hooks/use-authz.ts"],"sourcesContent":["import { buildSessionKey } from \"@authdog/node-commons\";\nimport {\n createContext,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\nimport { getPublicKeyPayload } from \"./commons\";\nimport { type AuthdogStorage, inMemoryStorage } from \"./storage\";\n\nexport interface AuthdogContextValue {\n /** The Authdog public key (`pk_…`) the app was configured with. */\n readonly publicKey: string;\n /** `true` while the persisted token is being loaded from storage on mount. */\n readonly isLoading: boolean;\n /** The current session token, or `null` when signed out. */\n readonly token: string | null;\n /** Async-aware token storage backing this provider. */\n readonly storage: AuthdogStorage;\n /** The storage key the session token is persisted under. */\n readonly storageKey: string;\n /**\n * Persists (or clears, when passed `null`) the token in storage and updates\n * the in-memory state. The returned promise resolves once storage settles.\n */\n setToken: (token: string | null) => Promise<void>;\n}\n\nexport const AuthdogContext = createContext<AuthdogContextValue | null>(null);\n\n/**\n * Resolves the storage key for a given public key. Falls back to a stable\n * default if the key cannot be parsed, so a misconfigured key never throws\n * during render.\n */\nconst resolveStorageKey = (publicKey: string): string => {\n try {\n return buildSessionKey(getPublicKeyPayload(publicKey).environmentId);\n } catch {\n return \"token\";\n }\n};\n\nexport interface AuthdogProviderProps {\n /** Your Authdog public key (`pk_…`). */\n publicKey: string;\n /**\n * Token storage. Defaults to an in-memory store (token is lost on restart).\n * Provide a secure store in production — see `createSecureStoreAdapter`.\n */\n storage?: AuthdogStorage;\n children?: ReactNode;\n}\n\nexport const AuthdogProvider = ({\n publicKey,\n storage,\n children,\n}: AuthdogProviderProps) => {\n const [isLoading, setIsLoading] = useState(true);\n const [token, setTokenState] = useState<string | null>(null);\n\n // Keep storage / key stable across renders so effects don't re-run and the\n // returned context identity only changes when state actually changes.\n const storageRef = useRef<AuthdogStorage>(storage ?? inMemoryStorage());\n if (storage && storageRef.current !== storage) {\n storageRef.current = storage;\n }\n const storageKey = useMemo(() => resolveStorageKey(publicKey), [publicKey]);\n\n useEffect(() => {\n let cancelled = false;\n\n (async () => {\n try {\n const existing = await storageRef.current.getItem(storageKey);\n if (!cancelled && existing) {\n setTokenState(existing);\n }\n } catch {\n // A storage read failure is non-fatal: treat it as \"signed out\".\n } finally {\n if (!cancelled) {\n setIsLoading(false);\n }\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [storageKey]);\n\n const setToken = useCallback(\n async (next: string | null) => {\n setTokenState(next);\n if (next === null) {\n await storageRef.current.removeItem(storageKey);\n } else {\n await storageRef.current.setItem(storageKey, next);\n }\n },\n [storageKey],\n );\n\n const value = useMemo<AuthdogContextValue>(\n () => ({\n publicKey,\n isLoading,\n token,\n storage: storageRef.current,\n storageKey,\n setToken,\n }),\n [publicKey, isLoading, token, storageKey, setToken],\n );\n\n return (\n <AuthdogContext.Provider value={value}>{children}</AuthdogContext.Provider>\n );\n};\n","import {\n validateAndParsePublicKey,\n type PublicKeyPayload,\n} from \"@authdog/node-commons\";\n\nexport type { PublicKeyPayload };\n\n/**\n * Decodes and validates an Authdog public key. Delegates to the hardened\n * shared parser in @authdog/node-commons, which validates the payload and\n * enforces a trusted identity-host allowlist (SSRF / token-exfiltration\n * protection) rather than blindly decoding base64/JSON.\n */\nexport const getPublicKeyPayload = (publicKey: string): PublicKeyPayload => {\n return validateAndParsePublicKey(publicKey);\n};\n\n/** JWT shape: three base64url segments separated by dots. */\nexport const JWT_PATTERN =\n /^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/;\n\n/**\n * Extracts the `?token=` query parameter from a redirect/deep-link URL.\n * Returns `null` when the URL is malformed or carries no token.\n */\nexport const getTokenFromUri = (url: string): string | null => {\n try {\n return new URL(url).searchParams.get(\"token\");\n } catch {\n return null;\n }\n};\n","/**\n * Pluggable, asynchronous token storage.\n *\n * Unlike the browser SDKs (which can read `localStorage` synchronously), mobile\n * secure storage is always asynchronous. The SDK therefore talks to storage\n * exclusively through this async interface so any backing store can be used:\n * `expo-secure-store`, `@react-native-async-storage/async-storage`, the\n * Keychain/Keystore, etc.\n *\n * For real apps you SHOULD use a hardware-backed secure store\n * (`expo-secure-store`) so the session token is encrypted at rest rather than\n * sitting in plain AsyncStorage.\n */\nexport interface AuthdogStorage {\n getItem: (key: string) => Promise<string | null>;\n setItem: (key: string, value: string) => Promise<void>;\n removeItem: (key: string) => Promise<void>;\n}\n\n/**\n * Minimal in-memory storage. Intended as a safe default and for tests — the\n * token does NOT survive an app restart and is NOT encrypted at rest. Provide\n * a real `AuthdogStorage` (e.g. via {@link createSecureStoreAdapter}) in\n * production.\n */\nexport const inMemoryStorage = (): AuthdogStorage => {\n const store = new Map<string, string>();\n return {\n getItem: async (key) => store.get(key) ?? null,\n setItem: async (key, value) => {\n store.set(key, value);\n },\n removeItem: async (key) => {\n store.delete(key);\n },\n };\n};\n\n/**\n * The subset of the `expo-secure-store` module the adapter relies on. Declared\n * structurally so this package never has to depend on Expo directly.\n */\nexport interface SecureStoreLike {\n getItemAsync: (key: string) => Promise<string | null>;\n setItemAsync: (key: string, value: string) => Promise<void>;\n deleteItemAsync: (key: string) => Promise<void>;\n}\n\n/**\n * Adapts the `expo-secure-store` module to {@link AuthdogStorage}.\n *\n * @example\n * import * as SecureStore from \"expo-secure-store\";\n * import { createSecureStoreAdapter } from \"@authdog/react-native\";\n *\n * const storage = createSecureStoreAdapter(SecureStore);\n */\nexport const createSecureStoreAdapter = (\n secureStore: SecureStoreLike,\n): AuthdogStorage => ({\n getItem: (key) => secureStore.getItemAsync(key),\n setItem: (key, value) => secureStore.setItemAsync(key, value),\n removeItem: (key) => secureStore.deleteItemAsync(key),\n});\n","import { useMemo } from \"react\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useSession = () => {\n const context = useAuthdogContext(\"useSession\");\n\n const session = useMemo(\n () => ({\n token: context.token,\n isAuthenticated: !!context.token,\n }),\n [context.token],\n );\n\n return {\n session,\n isLoading: context.isLoading,\n };\n};\n","import { useContext } from \"react\";\nimport { AuthdogContext, type AuthdogContextValue } from \"../provider\";\n\n/**\n * Internal helper: returns the Authdog context or throws a clear error when a\n * hook is used outside `<AuthdogProvider>`. The `hookName` is interpolated so\n * the message points at the offending hook.\n */\nexport const useAuthdogContext = (hookName: string): AuthdogContextValue => {\n const context = useContext(AuthdogContext);\n if (!context) {\n throw new Error(`${hookName} must be used within AuthdogProvider`);\n }\n return context;\n};\n","import { useCallback, useState } from \"react\";\nimport { fetchUserData, validatePublicKey } from \"../session\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useUser = () => {\n const context = useAuthdogContext(\"useUser\");\n const [user, setUser] = useState<unknown>(null);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n // The public key is supplied to the provider, so callers don't need to pass\n // it again — it is read from context.\n const fetchUser = useCallback(async () => {\n if (!context.token) {\n return null;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n validatePublicKey(context.publicKey);\n const userData = await fetchUserData(context.publicKey, context.token);\n const nextUser = userData?.user ?? null;\n setUser(nextUser);\n return nextUser;\n } catch (err) {\n setError(err as Error);\n return null;\n } finally {\n setIsLoading(false);\n }\n }, [context.publicKey, context.token]);\n\n return {\n user,\n isLoading,\n error,\n isAuthenticated: !!context.token && !!user,\n fetchUser,\n };\n};\n","import { getPublicKeyPayload } from \"./commons\";\n\ninterface IFetchUserData {\n user: {\n id: string;\n environmentId: string;\n externalId: string;\n userName: string;\n displayName: string;\n nickName: string;\n profileUrl: string;\n title: string;\n userType: string;\n preferredLanguage: string | null;\n locale: string | null;\n timezone: string | null;\n active: boolean;\n provider: string;\n lastLogin: string;\n createdAt: string;\n updatedAt: string;\n names: {\n id: string;\n userId: string;\n formatted: string | null;\n familyName: string;\n givenName: string;\n middleName: string | null;\n honorificPrefix: string | null;\n honorificSuffix: string | null;\n createdAt: string;\n updatedAt: string;\n };\n addresses: [];\n emails: {\n value: string;\n primary: boolean;\n type: string;\n }[];\n phoneNumbers: [];\n ims: [];\n photos: {\n value: string;\n type: string;\n }[];\n };\n meta: {\n code: number;\n message: string;\n };\n}\n\nexport const validatePublicKey = (publicKey: string) => {\n if (!publicKey) {\n throw new Error(\"Public key is not defined\");\n }\n\n if (!publicKey.startsWith(\"pk_\")) {\n throw new Error(\"Invalid public key\");\n }\n};\n\n/**\n * Fetches the current user from the identity host's OIDC `userinfo` endpoint.\n * The identity host is taken from the validated public key payload (which the\n * shared parser already constrains to the trusted-host allowlist), so the\n * bearer token is never sent to an attacker-controlled origin.\n */\nexport const fetchUserData = async (\n publicKey: string,\n token: string,\n): Promise<IFetchUserData | null> => {\n validatePublicKey(publicKey);\n const publicKeyObj = getPublicKeyPayload(publicKey);\n const userData = await fetch(\n `${publicKeyObj.identityHost}/oidc/${publicKeyObj.environmentId}/userinfo`,\n {\n headers: {\n authorization: `Bearer ${token}`,\n },\n },\n );\n\n if (!userData.ok) {\n throw new Error(\"Failed to fetch user info\");\n }\n\n return (await userData.json()) as IFetchUserData;\n};\n\nexport type { IFetchUserData };\n","import { useCallback, useState } from \"react\";\nimport { Linking } from \"react-native\";\nimport { buildAuthorizeUrl } from \"../auth-url\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useSignIn = () => {\n const context = useAuthdogContext(\"useSignIn\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n /**\n * Opens the hosted sign-in flow in the system browser. `redirectUrl` MUST be\n * a deep link registered by your app (e.g. `myapp://callback`) so the\n * identity server can return the user — pass it to `handleRedirect` to\n * complete sign-in.\n */\n const signIn = useCallback(\n async (redirectUrl: string) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const authUrl = buildAuthorizeUrl(context.publicKey, { redirectUrl });\n await Linking.openURL(authUrl);\n } catch (err) {\n setError(err as Error);\n } finally {\n setIsLoading(false);\n }\n },\n [context.publicKey],\n );\n\n return { signIn, isLoading, error };\n};\n","import { getPublicKeyPayload } from \"./commons\";\n\nexport interface BuildAuthorizeUrlOptions {\n /**\n * The app's deep-link/callback URL the identity server redirects back to,\n * e.g. `myapp://callback`. This is registered as the OIDC `redirect_uri`.\n */\n redirectUrl: string;\n /** When `true`, hints the hosted UI to show the sign-up flow. */\n signup?: boolean;\n}\n\n/**\n * Builds the hosted OIDC authorize URL for the given public key.\n *\n * The identity host comes from the validated public key payload (constrained to\n * the trusted-host allowlist by the shared parser), so a crafted key cannot\n * point the login flow at an attacker-controlled origin.\n */\nexport const buildAuthorizeUrl = (\n publicKey: string,\n { redirectUrl, signup }: BuildAuthorizeUrlOptions,\n): string => {\n const payload = getPublicKeyPayload(publicKey);\n\n const authUrl = new URL(\n `${payload.identityHost}/oidc/${payload.environmentId}/authorize`,\n );\n authUrl.searchParams.set(\"client_id\", publicKey);\n authUrl.searchParams.set(\"response_type\", \"code\");\n authUrl.searchParams.set(\"scope\", \"openid profile email\");\n authUrl.searchParams.set(\"redirect_uri\", redirectUrl);\n if (signup) {\n authUrl.searchParams.set(\"prompt\", \"signup\");\n }\n\n return authUrl.toString();\n};\n","import { useCallback, useState } from \"react\";\nimport { Linking } from \"react-native\";\nimport { buildAuthorizeUrl } from \"../auth-url\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useSignUp = () => {\n const context = useAuthdogContext(\"useSignUp\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n /**\n * Opens the hosted sign-up flow (`prompt=signup`) in the system browser.\n * `redirectUrl` MUST be a deep link registered by your app.\n */\n const signUp = useCallback(\n async (redirectUrl: string) => {\n setIsLoading(true);\n setError(null);\n\n try {\n const authUrl = buildAuthorizeUrl(context.publicKey, {\n redirectUrl,\n signup: true,\n });\n await Linking.openURL(authUrl);\n } catch (err) {\n setError(err as Error);\n } finally {\n setIsLoading(false);\n }\n },\n [context.publicKey],\n );\n\n return { signUp, isLoading, error };\n};\n","import { useCallback, useState } from \"react\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useSignOut = () => {\n const context = useAuthdogContext(\"useSignOut\");\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n /**\n * Clears the session token from both in-memory state and the backing\n * storage. There is no browser to redirect on native, so callers are\n * responsible for any post-sign-out navigation.\n */\n const signOut = useCallback(async () => {\n setIsLoading(true);\n setError(null);\n\n try {\n await context.setToken(null);\n } catch (err) {\n setError(err as Error);\n } finally {\n setIsLoading(false);\n }\n }, [context]);\n\n return { signOut, isLoading, error };\n};\n","import { useCallback } from \"react\";\nimport { JWT_PATTERN, getTokenFromUri } from \"../commons\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\nexport const useRedirectHandler = () => {\n const context = useAuthdogContext(\"useRedirectHandler\");\n\n /**\n * Completes sign-in from a returned deep link (e.g. the URL delivered to\n * `Linking.addEventListener(\"url\", …)` or `Linking.getInitialURL()`).\n *\n * The `?token=` value is validated against the JWT shape BEFORE it is\n * persisted, so an attacker who can craft a deep link cannot get arbitrary\n * data written into secure storage. Returns the token on success, otherwise\n * `null`.\n */\n const handleRedirect = useCallback(\n async (url: string): Promise<string | null> => {\n const token = getTokenFromUri(url);\n\n // Only persist values that look like a JWT.\n if (!token || !JWT_PATTERN.test(token)) {\n return null;\n }\n\n await context.setToken(token);\n return token;\n },\n [context],\n );\n\n return { handleRedirect };\n};\n","import { useCallback, useState } from \"react\";\nimport { useAuthdogContext } from \"./use-authdog-context\";\n\n/**\n * ⚠️ PRESENTATIONAL ONLY — NOT A SECURITY BOUNDARY.\n *\n * This hook fetches a permission list to drive UI affordances (showing or\n * hiding buttons, tabs, screens, etc.). It runs entirely on the device and is\n * therefore trivially bypassable by anyone who controls the client. It MUST\n * NOT be used to gate access to data or actions.\n *\n * Every protected operation MUST be independently enforced server-side.\n */\n\nexport interface UseAuthzOptions {\n /**\n * Absolute URL of the endpoint that returns the current user's permissions.\n * Defaults to \"/api/permissions\" — note that on native you almost always\n * need a fully-qualified URL. This endpoint is informational only;\n * authorization must still be enforced on every protected server endpoint.\n */\n permissionsUrl?: string;\n}\n\nexport const useAuthz = (options: UseAuthzOptions = {}) => {\n const context = useAuthdogContext(\"useAuthz\");\n const [permissions, setPermissions] = useState<string[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const permissionsUrl = options.permissionsUrl ?? \"/api/permissions\";\n\n const fetchPermissions = useCallback(async () => {\n if (!context.token) {\n return [];\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n const response = await fetch(permissionsUrl, {\n headers: {\n Authorization: `Bearer ${context.token}`,\n },\n });\n\n // Distinguish an authentication failure from an empty permission list.\n // A 401 means the session is invalid/expired and should surface as an\n // error rather than being silently coerced into \"no permissions\".\n if (response.status === 401) {\n setPermissions([]);\n throw new Error(\"Unauthorized: authentication failed (401)\");\n }\n\n if (!response.ok) {\n throw new Error(\n `Failed to fetch permissions (status ${response.status})`,\n );\n }\n\n const data = (await response.json()) as { permissions?: string[] };\n const next: string[] = data.permissions ?? [];\n setPermissions(next);\n return next;\n } catch (err) {\n setError(err as Error);\n return [];\n } finally {\n setIsLoading(false);\n }\n }, [context.token, permissionsUrl]);\n\n /**\n * ⚠️ PRESENTATIONAL ONLY. Returns whether the locally-cached permission list\n * contains `permission`. For UI hints only and is bypassable; never rely on\n * it as an access-control check. Enforce permissions server-side.\n */\n const hasPermission = useCallback(\n (permission: string) => permissions.includes(permission),\n [permissions],\n );\n\n const hasAnyPermission = useCallback(\n (list: string[]) => list.some((p) => permissions.includes(p)),\n [permissions],\n );\n\n const hasAllPermissions = useCallback(\n (list: string[]) => list.every((p) => permissions.includes(p)),\n [permissions],\n );\n\n return {\n permissions,\n isLoading,\n error,\n fetchPermissions,\n hasPermission,\n hasAnyPermission,\n hasAllPermissions,\n };\n};\n"],"mappings":"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","names":["buildSessionKey","createContext","useCallback","useEffect","useMemo","useRef","useState","validateAndParsePublicKey","getPublicKeyPayload","publicKey","JWT_PATTERN","getTokenFromUri","url","inMemoryStorage","store","key","value","createSecureStoreAdapter","secureStore","jsx","AuthdogContext","createContext","resolveStorageKey","publicKey","buildSessionKey","getPublicKeyPayload","AuthdogProvider","storage","children","isLoading","setIsLoading","useState","token","setTokenState","storageRef","useRef","inMemoryStorage","storageKey","useMemo","useEffect","cancelled","existing","setToken","useCallback","next","value","useMemo","useContext","useAuthdogContext","hookName","context","useContext","AuthdogContext","useSession","context","useAuthdogContext","useMemo","useCallback","useState","validatePublicKey","publicKey","fetchUserData","token","publicKeyObj","getPublicKeyPayload","userData","useUser","context","useAuthdogContext","user","setUser","useState","isLoading","setIsLoading","error","setError","fetchUser","useCallback","validatePublicKey","nextUser","fetchUserData","err","useCallback","useState","Linking","buildAuthorizeUrl","publicKey","redirectUrl","signup","payload","getPublicKeyPayload","authUrl","useSignIn","context","useAuthdogContext","isLoading","setIsLoading","useState","error","setError","useCallback","redirectUrl","authUrl","buildAuthorizeUrl","Linking","err","useCallback","useState","Linking","useSignUp","context","useAuthdogContext","isLoading","setIsLoading","useState","error","setError","useCallback","redirectUrl","authUrl","buildAuthorizeUrl","Linking","err","useCallback","useState","useSignOut","context","useAuthdogContext","isLoading","setIsLoading","useState","error","setError","useCallback","err","useCallback","useRedirectHandler","context","useAuthdogContext","useCallback","url","token","getTokenFromUri","JWT_PATTERN","useCallback","useState","useAuthz","options","context","useAuthdogContext","permissions","setPermissions","useState","isLoading","setIsLoading","error","setError","permissionsUrl","fetchPermissions","useCallback","response","next","err","hasPermission","permission","hasAnyPermission","list","p","hasAllPermissions"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@authdog/react-native",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Authdog React Native / Expo SDK",
|
|
5
|
+
"source": "src/index.ts",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.mjs",
|
|
13
|
+
"require": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist/"
|
|
18
|
+
],
|
|
19
|
+
"sideEffects": false,
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/authdog-labs/web-sdk.git",
|
|
23
|
+
"directory": "packages/react-native"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://github.com/authdog-labs/web-sdk/tree/main/packages/react-native#readme",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/authdog-labs/web-sdk/issues"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"format": "prettier --config .prettierrc.json --write \"**/*.{ts,tsx,md}\"",
|
|
31
|
+
"type-check": "tsc",
|
|
32
|
+
"clean": "rm -rf dist",
|
|
33
|
+
"build": "bun run clean && tsup",
|
|
34
|
+
"ship": "bun run build && bun publish --access public"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@authdog/node-commons": "workspace:*"
|
|
38
|
+
},
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": "^18.2.0 || ^19.0.0",
|
|
41
|
+
"react-native": ">=0.74.0"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"@types/node": "^22.14.1",
|
|
45
|
+
"@types/react": "^19.1.0",
|
|
46
|
+
"prettier": "^3.4.2",
|
|
47
|
+
"react": "^19.1.0",
|
|
48
|
+
"react-native": "^0.79.0",
|
|
49
|
+
"tsup": "^8.3.5",
|
|
50
|
+
"typescript": "^5.7.2",
|
|
51
|
+
"vitest": "^2.1.8"
|
|
52
|
+
},
|
|
53
|
+
"publishConfig": {
|
|
54
|
+
"registry": "https://registry.npmjs.org/",
|
|
55
|
+
"access": "public"
|
|
56
|
+
}
|
|
57
|
+
}
|