@ankhorage/studio 0.5.1 → 0.6.1
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 +1 -1
- package/dist/host/layout/auth/resolveAuthLayoutPlan.d.ts +16 -2
- package/dist/host/layout/auth/resolveAuthLayoutPlan.d.ts.map +1 -1
- package/dist/host/layout/auth/resolveAuthLayoutPlan.js +78 -4
- package/dist/host/layout/auth/resolveAuthLayoutPlan.js.map +1 -1
- package/dist/host/layout/layoutGenerator.d.ts.map +1 -1
- package/dist/host/layout/layoutGenerator.js +16 -2
- package/dist/host/layout/layoutGenerator.js.map +1 -1
- package/dist/host/layout/templates/auth/adapter.d.ts +1 -0
- package/dist/host/layout/templates/auth/adapter.d.ts.map +1 -1
- package/dist/host/layout/templates/auth/adapter.js +5 -0
- package/dist/host/layout/templates/auth/adapter.js.map +1 -1
- package/dist/host/layout/templates/auth/callback.d.ts +5 -0
- package/dist/host/layout/templates/auth/callback.d.ts.map +1 -0
- package/dist/host/layout/templates/auth/callback.js +114 -0
- package/dist/host/layout/templates/auth/callback.js.map +1 -0
- package/dist/host/layout/templates/auth/oauth.d.ts +3 -0
- package/dist/host/layout/templates/auth/oauth.d.ts.map +1 -0
- package/dist/host/layout/templates/auth/oauth.js +268 -0
- package/dist/host/layout/templates/auth/oauth.js.map +1 -0
- package/dist/host/layout/templates/auth/screen.d.ts +3 -0
- package/dist/host/layout/templates/auth/screen.d.ts.map +1 -1
- package/dist/host/layout/templates/auth/screen.js +99 -94
- package/dist/host/layout/templates/auth/screen.js.map +1 -1
- package/dist/host/layout/templates/auth/session.d.ts.map +1 -1
- package/dist/host/layout/templates/auth/session.js +125 -127
- package/dist/host/layout/templates/auth/session.js.map +1 -1
- package/dist/host/layout/templates/auth/signOut.js +1 -1
- package/dist/host/layout/templates/index.d.ts +2 -0
- package/dist/host/layout/templates/index.d.ts.map +1 -1
- package/dist/host/layout/templates/index.js +2 -0
- package/dist/host/layout/templates/index.js.map +1 -1
- package/dist/host/layout/templates/rootLayout.d.ts.map +1 -1
- package/dist/host/layout/templates/rootLayout.js +12 -1
- package/dist/host/layout/templates/rootLayout.js.map +1 -1
- package/dist/host/orchestrator/templates.d.ts +2 -0
- package/dist/host/orchestrator/templates.d.ts.map +1 -1
- package/dist/host/orchestrator/templates.js +12 -2
- package/dist/host/orchestrator/templates.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { escapeStringLiteral } from '../../utils/escapeStringLiteral';
|
|
2
|
+
export function getAuthOAuthRuntimeTs(args) {
|
|
3
|
+
const callbackRoute = escapeStringLiteral(args.callbackRoute);
|
|
4
|
+
const providers = JSON.stringify(args.providers);
|
|
5
|
+
return `import type {
|
|
6
|
+
AuthOAuthCompletionResult,
|
|
7
|
+
AuthOAuthProviderId,
|
|
8
|
+
AuthOAuthTransportCancellationReason,
|
|
9
|
+
} from '@ankhorage/contracts/auth';
|
|
10
|
+
import * as Linking from 'expo-linking';
|
|
11
|
+
import * as WebBrowser from 'expo-web-browser';
|
|
12
|
+
import { Platform } from 'react-native';
|
|
13
|
+
|
|
14
|
+
import { authAdapter } from './adapter';
|
|
15
|
+
import {
|
|
16
|
+
authSessionStorage,
|
|
17
|
+
getStoredAuthSession,
|
|
18
|
+
} from './session';
|
|
19
|
+
|
|
20
|
+
const OAUTH_CALLBACK_ROUTE = '${callbackRoute}';
|
|
21
|
+
const OAUTH_TRANSPORT_ATTEMPT_KEY = 'ankh.auth.oauth.transport.v1';
|
|
22
|
+
const GENERATED_OAUTH_PROVIDERS = ${providers} as const;
|
|
23
|
+
|
|
24
|
+
export const generatedOAuthProviderItems = GENERATED_OAUTH_PROVIDERS.map((provider) => ({
|
|
25
|
+
id: provider.id,
|
|
26
|
+
label: provider.label,
|
|
27
|
+
...(provider.icon ? { icon: provider.icon } : {}),
|
|
28
|
+
}));
|
|
29
|
+
|
|
30
|
+
export type GeneratedOAuthTransportOutcome =
|
|
31
|
+
| { status: 'authenticated' }
|
|
32
|
+
| { status: 'cancelled'; message: string }
|
|
33
|
+
| { status: 'error'; message: string; recoverable: boolean };
|
|
34
|
+
|
|
35
|
+
interface StoredTransportAttempt {
|
|
36
|
+
attemptId: string;
|
|
37
|
+
provider: AuthOAuthProviderId;
|
|
38
|
+
redirectUri: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let activeAuthorization: Promise<GeneratedOAuthTransportOutcome> | null = null;
|
|
42
|
+
|
|
43
|
+
export function startOAuthAuthorization(
|
|
44
|
+
providerId: string,
|
|
45
|
+
): Promise<GeneratedOAuthTransportOutcome> {
|
|
46
|
+
activeAuthorization ??= runOAuthAuthorization(providerId).finally(() => {
|
|
47
|
+
activeAuthorization = null;
|
|
48
|
+
});
|
|
49
|
+
return activeAuthorization;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function runOAuthAuthorization(
|
|
53
|
+
providerId: string,
|
|
54
|
+
): Promise<GeneratedOAuthTransportOutcome> {
|
|
55
|
+
const oauth = authAdapter.oauth;
|
|
56
|
+
if (!oauth) {
|
|
57
|
+
return {
|
|
58
|
+
status: 'error',
|
|
59
|
+
message: 'OAuth is not available in this app configuration.',
|
|
60
|
+
recoverable: true,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const provider = GENERATED_OAUTH_PROVIDERS.find((entry) => entry.id === providerId);
|
|
65
|
+
if (!provider) {
|
|
66
|
+
return {
|
|
67
|
+
status: 'error',
|
|
68
|
+
message: 'This OAuth provider is not enabled.',
|
|
69
|
+
recoverable: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const redirectUri = resolveOAuthRedirectUri();
|
|
74
|
+
const started = await oauth.startAuthorization({
|
|
75
|
+
provider: provider.id,
|
|
76
|
+
redirectUri,
|
|
77
|
+
scopes: provider.scopes,
|
|
78
|
+
queryParams: provider.queryParams,
|
|
79
|
+
});
|
|
80
|
+
if (!started.ok) {
|
|
81
|
+
return {
|
|
82
|
+
status: 'error',
|
|
83
|
+
message: started.error.message,
|
|
84
|
+
recoverable: started.error.recoverable,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
await writeTransportAttempt({
|
|
90
|
+
attemptId: started.data.attemptId,
|
|
91
|
+
provider: started.data.provider,
|
|
92
|
+
redirectUri: started.data.redirectUri,
|
|
93
|
+
});
|
|
94
|
+
} catch {
|
|
95
|
+
await cancelOAuthAttempt(started.data.attemptId, 'user_cancelled');
|
|
96
|
+
await clearTransportAttempt();
|
|
97
|
+
return {
|
|
98
|
+
status: 'error',
|
|
99
|
+
message: 'The OAuth authorization attempt could not be persisted.',
|
|
100
|
+
recoverable: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let browserResult: WebBrowser.WebBrowserAuthSessionResult;
|
|
105
|
+
try {
|
|
106
|
+
browserResult = await WebBrowser.openAuthSessionAsync(
|
|
107
|
+
started.data.authorizationUrl,
|
|
108
|
+
started.data.redirectUri,
|
|
109
|
+
);
|
|
110
|
+
} catch {
|
|
111
|
+
await cancelOAuthAttempt(started.data.attemptId, 'browser_dismissed');
|
|
112
|
+
await clearTransportAttempt();
|
|
113
|
+
return {
|
|
114
|
+
status: 'error',
|
|
115
|
+
message: 'The system authentication browser could not be opened.',
|
|
116
|
+
recoverable: true,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (browserResult.type === 'success' && typeof browserResult.url === 'string') {
|
|
121
|
+
return completeOAuthCallback(browserResult.url);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const cancellationReason =
|
|
125
|
+
browserResult.type === 'dismiss' ? 'browser_dismissed' : 'user_cancelled';
|
|
126
|
+
const completed = await oauth.completeAuthorization({
|
|
127
|
+
attemptId: started.data.attemptId,
|
|
128
|
+
response: { type: 'cancelled', reason: cancellationReason },
|
|
129
|
+
});
|
|
130
|
+
await clearTransportAttempt();
|
|
131
|
+
return toTransportOutcome(completed);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function completeOAuthCallback(
|
|
135
|
+
callbackUrl: string,
|
|
136
|
+
): Promise<GeneratedOAuthTransportOutcome> {
|
|
137
|
+
const oauth = authAdapter.oauth;
|
|
138
|
+
if (!oauth) {
|
|
139
|
+
return {
|
|
140
|
+
status: 'error',
|
|
141
|
+
message: 'OAuth is not available in this app configuration.',
|
|
142
|
+
recoverable: true,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const attempt = await readTransportAttempt();
|
|
147
|
+
if (!attempt) {
|
|
148
|
+
return {
|
|
149
|
+
status: 'error',
|
|
150
|
+
message: 'The OAuth authorization attempt was not found or has expired.',
|
|
151
|
+
recoverable: true,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const completed = await oauth.completeAuthorization({
|
|
156
|
+
attemptId: attempt.attemptId,
|
|
157
|
+
response: { type: 'callback', url: callbackUrl },
|
|
158
|
+
});
|
|
159
|
+
await clearTransportAttempt();
|
|
160
|
+
|
|
161
|
+
if (
|
|
162
|
+
!completed.ok &&
|
|
163
|
+
completed.status === 'error' &&
|
|
164
|
+
completed.error.code === 'callback_already_completed' &&
|
|
165
|
+
getStoredAuthSession()
|
|
166
|
+
) {
|
|
167
|
+
return { status: 'authenticated' };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return toTransportOutcome(completed);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function resolveOAuthRedirectUri(): string {
|
|
174
|
+
const callbackPath = OAUTH_CALLBACK_ROUTE.replace(/^\/+/, '');
|
|
175
|
+
if (Platform.OS === 'web') {
|
|
176
|
+
const location = Reflect.get(globalThis, 'location');
|
|
177
|
+
if (
|
|
178
|
+
typeof location === 'object' &&
|
|
179
|
+
location !== null &&
|
|
180
|
+
typeof Reflect.get(location, 'origin') === 'string'
|
|
181
|
+
) {
|
|
182
|
+
return new URL(\`/\${callbackPath}\`, Reflect.get(location, 'origin')).toString();
|
|
183
|
+
}
|
|
184
|
+
throw new Error('Web OAuth requires a canonical browser origin.');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return Linking.createURL(callbackPath);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function toTransportOutcome(
|
|
191
|
+
result: AuthOAuthCompletionResult,
|
|
192
|
+
): GeneratedOAuthTransportOutcome {
|
|
193
|
+
if (result.ok) {
|
|
194
|
+
return { status: 'authenticated' };
|
|
195
|
+
}
|
|
196
|
+
if (result.status === 'cancelled') {
|
|
197
|
+
return {
|
|
198
|
+
status: 'cancelled',
|
|
199
|
+
message:
|
|
200
|
+
result.reason === 'provider_denied'
|
|
201
|
+
? 'Authorization was declined by the provider.'
|
|
202
|
+
: 'Authorization was cancelled.',
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
return {
|
|
206
|
+
status: 'error',
|
|
207
|
+
message: result.error.message,
|
|
208
|
+
recoverable: result.error.recoverable,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function cancelOAuthAttempt(
|
|
213
|
+
attemptId: string,
|
|
214
|
+
reason: AuthOAuthTransportCancellationReason,
|
|
215
|
+
): Promise<void> {
|
|
216
|
+
const oauth = authAdapter.oauth;
|
|
217
|
+
if (!oauth) return;
|
|
218
|
+
try {
|
|
219
|
+
await oauth.completeAuthorization({
|
|
220
|
+
attemptId,
|
|
221
|
+
response: { type: 'cancelled', reason },
|
|
222
|
+
});
|
|
223
|
+
} catch {
|
|
224
|
+
// Best-effort cleanup must not replace the original transport error.
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function writeTransportAttempt(attempt: StoredTransportAttempt): Promise<void> {
|
|
229
|
+
await authSessionStorage.setItem(OAUTH_TRANSPORT_ATTEMPT_KEY, JSON.stringify(attempt));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function readTransportAttempt(): Promise<StoredTransportAttempt | null> {
|
|
233
|
+
const raw = await authSessionStorage.getItem(OAUTH_TRANSPORT_ATTEMPT_KEY);
|
|
234
|
+
if (!raw) return null;
|
|
235
|
+
try {
|
|
236
|
+
const value: unknown = JSON.parse(raw);
|
|
237
|
+
if (!isRecord(value)) return null;
|
|
238
|
+
const attemptId = Reflect.get(value, 'attemptId');
|
|
239
|
+
const provider = Reflect.get(value, 'provider');
|
|
240
|
+
const redirectUri = Reflect.get(value, 'redirectUri');
|
|
241
|
+
const configuredProvider =
|
|
242
|
+
typeof provider === 'string'
|
|
243
|
+
? GENERATED_OAUTH_PROVIDERS.find((entry) => entry.id === provider)
|
|
244
|
+
: undefined;
|
|
245
|
+
return typeof attemptId === 'string' &&
|
|
246
|
+
configuredProvider !== undefined &&
|
|
247
|
+
typeof redirectUri === 'string'
|
|
248
|
+
? { attemptId, provider: configuredProvider.id, redirectUri }
|
|
249
|
+
: null;
|
|
250
|
+
} catch {
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async function clearTransportAttempt(): Promise<void> {
|
|
256
|
+
try {
|
|
257
|
+
await authSessionStorage.removeItem(OAUTH_TRANSPORT_ATTEMPT_KEY);
|
|
258
|
+
} catch {
|
|
259
|
+
// Cleanup failures are intentionally not surfaced with persisted state.
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
264
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
265
|
+
}
|
|
266
|
+
`;
|
|
267
|
+
}
|
|
268
|
+
//# sourceMappingURL=oauth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/oauth.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAEtE,MAAM,UAAU,qBAAqB,CAAC,IAAyB;IAC7D,MAAM,aAAa,GAAG,mBAAmB,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;IAEjD,OAAO;;;;;;;;;;;;;;;gCAeuB,aAAa;;oCAET,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoP5C,CAAC;AACF,CAAC"}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import type { GeneratedOAuthProviderPlan } from '../../auth/resolveAuthLayoutPlan';
|
|
1
2
|
export declare function getAuthScreenTsx(args: {
|
|
2
3
|
initialMode: 'signIn' | 'signUp';
|
|
3
4
|
screenName: string;
|
|
4
5
|
title?: string;
|
|
5
6
|
signInRoute: string;
|
|
6
7
|
signUpRoute: string;
|
|
8
|
+
postSignInRoute: string;
|
|
7
9
|
signInIdentifiers: string[];
|
|
8
10
|
signUpRequiredFields: string[];
|
|
9
11
|
signUpOptionalFields: string[];
|
|
10
12
|
signUpPolicy: 'autoSignIn' | 'requireVerification';
|
|
13
|
+
oauthProviders?: readonly GeneratedOAuthProviderPlan[];
|
|
11
14
|
}): string;
|
|
12
15
|
//# sourceMappingURL=screen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/screen.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"screen.d.ts","sourceRoot":"","sources":["../../../../../src/host/layout/templates/auth/screen.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAC;AAYnF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,WAAW,EAAE,QAAQ,GAAG,QAAQ,CAAC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,oBAAoB,EAAE,MAAM,EAAE,CAAC;IAC/B,YAAY,EAAE,YAAY,GAAG,qBAAqB,CAAC;IACnD,cAAc,CAAC,EAAE,SAAS,0BAA0B,EAAE,CAAC;CACxD,UAipBA"}
|