minutework 0.1.35 → 0.1.36
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/assets/templates/mobile-app/package.json +1 -0
- package/assets/templates/mobile-app/src/mw/client.ts +29 -232
- package/assets/templates/mobile-app/src/mw/session.ts +6 -2
- package/dist/runtime-package.d.ts +2 -1
- package/dist/runtime-package.js +12 -4
- package/dist/runtime-package.js.map +1 -1
- package/package.json +1 -1
- package/vendor/workspace-mcp/types.d.ts +27 -0
- package/assets/templates/mobile-app/src/mw/contracts.ts +0 -79
- package/assets/templates/mobile-app/src/mw/endpoints.ts +0 -42
|
@@ -1,251 +1,48 @@
|
|
|
1
|
-
// MinuteWork substrate. Thin layer — do not put product UI/logic here.
|
|
2
|
-
//
|
|
3
|
-
// MinuteWork native token client. Implements the browser-assisted device flow
|
|
4
|
-
// against the SHIPPED platform native-token endpoints (`/api/v1/native/session/*`).
|
|
5
|
-
//
|
|
6
|
-
// Authentication is owned by the MinuteWork platform. This client only *obtains
|
|
7
|
-
// and uses* a platform-issued bearer token — there is NO JWT minting, NO password
|
|
8
|
-
// handling, NO local user table, and NO parallel auth stack here. Token plaintext
|
|
9
|
-
// is never logged.
|
|
10
|
-
//
|
|
11
|
-
// ----------------------------------------------------------------------------
|
|
12
|
-
// Device flow (browser-assisted authorization):
|
|
13
|
-
// ----------------------------------------------------------------------------
|
|
14
|
-
// 1. authorize(): generate a PKCE code_verifier + S256 code_challenge, open the
|
|
15
|
-
// platform authorize URL in a system browser (`expo-web-browser`) with the
|
|
16
|
-
// app's deep-link redirect_uri + code_challenge + an anti-forgery `state`.
|
|
17
|
-
// The platform authenticates the human and redirects back to redirect_uri
|
|
18
|
-
// with a short-lived single-use `code` (and the echoed `state`).
|
|
19
|
-
// 2. exchange(code, verifier): POST {code, code_verifier, redirect_uri} to
|
|
20
|
-
// `/token-exchange/`; the platform returns the access/refresh token pair.
|
|
21
|
-
// 3. store the pair in the device keychain via `mwSession.setTokens(...)`.
|
|
22
|
-
// 4. for every platform call, send `Authorization: Bearer <access>`. On a 401,
|
|
23
|
-
// POST the stored refresh token to `/refresh/`, persist the rotated pair,
|
|
24
|
-
// and retry once.
|
|
25
|
-
// 5. logout(): POST `/logout/` to revoke, then `mwSession.clearTokens()`.
|
|
26
|
-
//
|
|
27
|
-
// This is the DIRECT platform path — there is no tenant-app BFF and no
|
|
28
|
-
// server-owned cookie in front of the mobile client.
|
|
29
|
-
|
|
30
1
|
import * as Crypto from "expo-crypto";
|
|
31
2
|
import * as Linking from "expo-linking";
|
|
32
3
|
import * as WebBrowser from "expo-web-browser";
|
|
33
|
-
|
|
34
4
|
import {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
type
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
import {
|
|
5
|
+
createNativeAuthClient,
|
|
6
|
+
type NativeBrowser,
|
|
7
|
+
type NativeCrypto,
|
|
8
|
+
} from "@minutework/native-auth";
|
|
9
|
+
|
|
10
|
+
import { mwEnv } from "@/mw/env";
|
|
41
11
|
import { mwSession } from "@/mw/session";
|
|
42
12
|
|
|
43
13
|
// The deep-link path the platform redirects back to. The scheme is read from
|
|
44
14
|
// `app.json` ("scheme") by expo-linking; the dev configures that scheme.
|
|
45
15
|
const REDIRECT_PATH = "auth/native-callback";
|
|
46
16
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
code: string;
|
|
51
|
-
codeVerifier: string;
|
|
52
|
-
redirectUri: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
function base64UrlFromBytes(bytes: Uint8Array): string {
|
|
56
|
-
let binary = "";
|
|
57
|
-
for (const byte of bytes) {
|
|
58
|
-
binary += String.fromCharCode(byte);
|
|
59
|
-
}
|
|
60
|
-
// btoa is available in the Hermes/React Native runtime.
|
|
61
|
-
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// PKCE code_verifier: 32 random bytes -> base64url (43 chars, no padding). This
|
|
65
|
-
// is URL-safe and whitespace-free, matching the platform's `.strip()`ed verifier
|
|
66
|
-
// and its 255-char cap.
|
|
67
|
-
function generateCodeVerifier(): string {
|
|
68
|
-
return base64UrlFromBytes(Crypto.getRandomBytes(32));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// S256 challenge: base64url(SHA-256(verifier)). Mirrors the platform's
|
|
72
|
-
// `build_pkce_code_challenge` (sha256 digest -> urlsafe base64 -> strip "=").
|
|
73
|
-
async function deriveCodeChallenge(codeVerifier: string): Promise<string> {
|
|
74
|
-
const digestBase64 = await Crypto.digestStringAsync(
|
|
75
|
-
Crypto.CryptoDigestAlgorithm.SHA256,
|
|
76
|
-
codeVerifier,
|
|
77
|
-
{ encoding: Crypto.CryptoEncoding.BASE64 },
|
|
78
|
-
);
|
|
79
|
-
return digestBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function buildAuthorizeUrl(params: {
|
|
83
|
-
redirectUri: string;
|
|
84
|
-
state: string;
|
|
85
|
-
codeChallenge: string;
|
|
86
|
-
}): string {
|
|
87
|
-
const url = new URL(platformNativeEndpoints.authorize);
|
|
88
|
-
url.searchParams.set("redirect_uri", params.redirectUri);
|
|
89
|
-
url.searchParams.set("state", params.state);
|
|
90
|
-
url.searchParams.set("code_challenge", params.codeChallenge);
|
|
91
|
-
return url.toString();
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async function postJson(url: string, body: Record<string, unknown>): Promise<Response> {
|
|
95
|
-
return fetch(url, {
|
|
96
|
-
method: "POST",
|
|
97
|
-
headers: {
|
|
98
|
-
"Content-Type": "application/json",
|
|
99
|
-
Accept: "application/json",
|
|
100
|
-
},
|
|
101
|
-
body: JSON.stringify(body),
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Surface a platform error without leaking response internals. The platform
|
|
106
|
-
// returns `{detail: "..."}` for native auth failures.
|
|
107
|
-
async function readErrorDetail(response: Response, fallback: string): Promise<string> {
|
|
108
|
-
try {
|
|
109
|
-
const data = (await response.json()) as { detail?: unknown };
|
|
110
|
-
if (data && typeof data.detail === "string" && data.detail.length > 0) {
|
|
111
|
-
return data.detail;
|
|
112
|
-
}
|
|
113
|
-
} catch {
|
|
114
|
-
// ignore non-JSON bodies
|
|
115
|
-
}
|
|
116
|
-
return `${fallback} (HTTP ${response.status})`;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
export const mwClient = {
|
|
120
|
-
// Step 1: open the platform authorize URL in a system browser and resolve with
|
|
121
|
-
// the single-use authorization code + the PKCE verifier to exchange it.
|
|
122
|
-
async authorize(): Promise<NativeAuthorizationResult> {
|
|
123
|
-
const redirectUri = Linking.createURL(REDIRECT_PATH);
|
|
124
|
-
const codeVerifier = generateCodeVerifier();
|
|
125
|
-
const codeChallenge = await deriveCodeChallenge(codeVerifier);
|
|
126
|
-
const state = base64UrlFromBytes(Crypto.getRandomBytes(16));
|
|
127
|
-
|
|
128
|
-
const authorizeUrl = buildAuthorizeUrl({ redirectUri, state, codeChallenge });
|
|
129
|
-
const result = await WebBrowser.openAuthSessionAsync(authorizeUrl, redirectUri);
|
|
130
|
-
|
|
131
|
-
if (result.type !== "success" || !result.url) {
|
|
132
|
-
throw new Error("Sign in was cancelled before the platform returned a code.");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const returned = new URL(result.url);
|
|
136
|
-
const returnedState = returned.searchParams.get("state");
|
|
137
|
-
if (returnedState !== state) {
|
|
138
|
-
// Anti-forgery: the platform must echo back the exact state we sent.
|
|
139
|
-
throw new Error("Sign in failed: callback state did not match the request.");
|
|
140
|
-
}
|
|
141
|
-
const code = returned.searchParams.get("code");
|
|
142
|
-
if (!code) {
|
|
143
|
-
throw new Error("Sign in failed: the platform callback did not include a code.");
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
return { code, codeVerifier, redirectUri };
|
|
147
|
-
},
|
|
148
|
-
|
|
149
|
-
// Step 2: exchange the authorization code for a platform-issued token pair and
|
|
150
|
-
// persist it to the device keychain.
|
|
151
|
-
async exchange(
|
|
152
|
-
code: string,
|
|
153
|
-
codeVerifier: string,
|
|
154
|
-
redirectUri: string,
|
|
155
|
-
): Promise<NativeTokenPair> {
|
|
156
|
-
const response = await postJson(platformNativeEndpoints.tokenExchange, {
|
|
157
|
-
code,
|
|
158
|
-
code_verifier: codeVerifier,
|
|
159
|
-
redirect_uri: redirectUri,
|
|
160
|
-
});
|
|
161
|
-
if (!response.ok) {
|
|
162
|
-
throw new Error(await readErrorDetail(response, "Token exchange failed"));
|
|
163
|
-
}
|
|
164
|
-
const pair = nativeTokenPairSchema.parse(await response.json());
|
|
165
|
-
await mwSession.setTokens(
|
|
166
|
-
pair.access_token,
|
|
167
|
-
pair.refresh_token,
|
|
168
|
-
pair.access_token_expires_at,
|
|
169
|
-
);
|
|
170
|
-
return pair;
|
|
17
|
+
const nativeCrypto: NativeCrypto = {
|
|
18
|
+
randomBytes(length: number): Uint8Array {
|
|
19
|
+
return Crypto.getRandomBytes(length);
|
|
171
20
|
},
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
if (!stored) {
|
|
178
|
-
throw new Error("No stored session to refresh. Sign in again.");
|
|
179
|
-
}
|
|
180
|
-
const response = await postJson(platformNativeEndpoints.refresh, {
|
|
181
|
-
refresh_token: stored.refresh,
|
|
182
|
-
});
|
|
183
|
-
if (!response.ok) {
|
|
184
|
-
// Refresh failure means the session is dead; clear it so the app routes
|
|
185
|
-
// back to login.
|
|
186
|
-
await mwSession.clearTokens();
|
|
187
|
-
throw new Error(await readErrorDetail(response, "Session refresh failed"));
|
|
188
|
-
}
|
|
189
|
-
const pair = nativeTokenPairSchema.parse(await response.json());
|
|
190
|
-
await mwSession.setTokens(
|
|
191
|
-
pair.access_token,
|
|
192
|
-
pair.refresh_token,
|
|
193
|
-
pair.access_token_expires_at,
|
|
21
|
+
async sha256Base64Url(input: string): Promise<string> {
|
|
22
|
+
const digestBase64 = await Crypto.digestStringAsync(
|
|
23
|
+
Crypto.CryptoDigestAlgorithm.SHA256,
|
|
24
|
+
input,
|
|
25
|
+
{ encoding: Crypto.CryptoEncoding.BASE64 },
|
|
194
26
|
);
|
|
195
|
-
return
|
|
27
|
+
return digestBase64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
196
28
|
},
|
|
29
|
+
};
|
|
197
30
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const stored = await mwSession.getTokens();
|
|
202
|
-
if (!stored) {
|
|
203
|
-
throw new Error("Not signed in.");
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const meOnce = async (accessToken: string): Promise<Response> =>
|
|
207
|
-
fetch(platformNativeEndpoints.me, {
|
|
208
|
-
method: "GET",
|
|
209
|
-
headers: {
|
|
210
|
-
Accept: "application/json",
|
|
211
|
-
Authorization: `Bearer ${accessToken}`,
|
|
212
|
-
},
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
let response = await meOnce(stored.access);
|
|
216
|
-
if (response.status === 401) {
|
|
217
|
-
// Single refresh-and-retry on an expired/invalid access token.
|
|
218
|
-
const rotated = await this.refresh();
|
|
219
|
-
response = await meOnce(rotated.access_token);
|
|
220
|
-
}
|
|
221
|
-
if (!response.ok) {
|
|
222
|
-
throw new Error(await readErrorDetail(response, "Failed to load session"));
|
|
223
|
-
}
|
|
224
|
-
return nativeSessionSchema.parse(await response.json());
|
|
31
|
+
const nativeBrowser: NativeBrowser = {
|
|
32
|
+
createRedirectUri(): string {
|
|
33
|
+
return Linking.createURL(REDIRECT_PATH);
|
|
225
34
|
},
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
async logout(): Promise<void> {
|
|
230
|
-
const stored = await mwSession.getTokens();
|
|
231
|
-
try {
|
|
232
|
-
if (stored) {
|
|
233
|
-
await fetch(platformNativeEndpoints.logout, {
|
|
234
|
-
method: "POST",
|
|
235
|
-
headers: {
|
|
236
|
-
"Content-Type": "application/json",
|
|
237
|
-
Accept: "application/json",
|
|
238
|
-
Authorization: `Bearer ${stored.access}`,
|
|
239
|
-
},
|
|
240
|
-
body: JSON.stringify({}),
|
|
241
|
-
});
|
|
242
|
-
}
|
|
243
|
-
} catch {
|
|
244
|
-
// Best-effort revoke; never block local sign-out on a network error.
|
|
245
|
-
} finally {
|
|
246
|
-
await mwSession.clearTokens();
|
|
247
|
-
}
|
|
35
|
+
async openAuthSession(url: string, redirectUri: string): Promise<string | null> {
|
|
36
|
+
const result = await WebBrowser.openAuthSessionAsync(url, redirectUri);
|
|
37
|
+
return result.type === "success" && result.url ? result.url : null;
|
|
248
38
|
},
|
|
249
39
|
};
|
|
250
40
|
|
|
41
|
+
export const mwClient = createNativeAuthClient({
|
|
42
|
+
platformBaseUrl: mwEnv.platformBaseUrl,
|
|
43
|
+
storage: mwSession,
|
|
44
|
+
crypto: nativeCrypto,
|
|
45
|
+
browser: nativeBrowser,
|
|
46
|
+
});
|
|
47
|
+
|
|
251
48
|
export type MwClient = typeof mwClient;
|
|
@@ -8,7 +8,11 @@
|
|
|
8
8
|
|
|
9
9
|
import * as SecureStore from "expo-secure-store";
|
|
10
10
|
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
storedTokensSchema,
|
|
13
|
+
type NativeTokenStorage,
|
|
14
|
+
type StoredTokens,
|
|
15
|
+
} from "@minutework/native-auth";
|
|
12
16
|
|
|
13
17
|
const TOKEN_KEY = "mw.native.token_pair";
|
|
14
18
|
|
|
@@ -45,6 +49,6 @@ export const mwSession = {
|
|
|
45
49
|
async clearTokens(): Promise<void> {
|
|
46
50
|
await SecureStore.deleteItemAsync(TOKEN_KEY, SECURE_STORE_OPTIONS);
|
|
47
51
|
},
|
|
48
|
-
};
|
|
52
|
+
} satisfies NativeTokenStorage;
|
|
49
53
|
|
|
50
54
|
export type MwSession = typeof mwSession;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { CompileGraph, RuntimeAppReleaseMetadata, SeedDataManifestDocument } from "@minutework/schema-compiler";
|
|
1
|
+
import type { CompileGraph, PlatformChannelManifestDocument, RuntimeAppReleaseMetadata, SeedDataManifestDocument } from "@minutework/schema-compiler";
|
|
2
2
|
export declare const MINUTEWORK_RUNTIME_APP_PACKAGE_VERSION = "MinuteWorkRuntimeAppPackageV1";
|
|
3
3
|
export type RuntimeAppSourceBundleFile = {
|
|
4
4
|
contentBase64: string;
|
|
@@ -14,6 +14,7 @@ export type RuntimeAppPackageArtifact = {
|
|
|
14
14
|
sha256: string;
|
|
15
15
|
};
|
|
16
16
|
release: RuntimeAppReleaseMetadata;
|
|
17
|
+
platformChannelManifest: PlatformChannelManifestDocument | null;
|
|
17
18
|
seedDataManifest: SeedDataManifestDocument | null;
|
|
18
19
|
sourceBundle: {
|
|
19
20
|
files: RuntimeAppSourceBundleFile[];
|
package/dist/runtime-package.js
CHANGED
|
@@ -16,6 +16,7 @@ export async function buildRuntimeAppPackage(options) {
|
|
|
16
16
|
sidecarRoot,
|
|
17
17
|
});
|
|
18
18
|
const seedDataManifest = options.compileGraph.documents.seedDataManifests[0] ?? null;
|
|
19
|
+
const platformChannelManifest = options.compileGraph.documents.platformChannelManifests[0] ?? null;
|
|
19
20
|
const artifactSetDigest = sha256Hex(JSON.stringify({
|
|
20
21
|
compileGraph: options.compileGraph.documents,
|
|
21
22
|
dependencyExport: {
|
|
@@ -24,7 +25,10 @@ export async function buildRuntimeAppPackage(options) {
|
|
|
24
25
|
},
|
|
25
26
|
release: options.releaseMetadata,
|
|
26
27
|
sourceBundle: {
|
|
27
|
-
files: sourceBundle.files.map((file) => ({
|
|
28
|
+
files: sourceBundle.files.map((file) => ({
|
|
29
|
+
path: file.path,
|
|
30
|
+
sha256: file.sha256,
|
|
31
|
+
})),
|
|
28
32
|
sha256: sourceBundle.sha256,
|
|
29
33
|
},
|
|
30
34
|
template,
|
|
@@ -35,6 +39,7 @@ export async function buildRuntimeAppPackage(options) {
|
|
|
35
39
|
compileGraph: options.compileGraph,
|
|
36
40
|
dependencyExport,
|
|
37
41
|
release: options.releaseMetadata,
|
|
42
|
+
platformChannelManifest,
|
|
38
43
|
seedDataManifest,
|
|
39
44
|
sourceBundle,
|
|
40
45
|
template,
|
|
@@ -43,13 +48,16 @@ export async function buildRuntimeAppPackage(options) {
|
|
|
43
48
|
}
|
|
44
49
|
export function buildRuntimePackagePayload(packageArtifact) {
|
|
45
50
|
const payload = JSON.parse(JSON.stringify(packageArtifact));
|
|
46
|
-
// Drop
|
|
47
|
-
// consumes
|
|
48
|
-
// apps/mwv3-runtime-dj/apps/runtime_app_host/deployment_services.py:416).
|
|
51
|
+
// Drop camelCase artifact-surface fields. The runtime install pipe
|
|
52
|
+
// consumes snake_case manifest keys.
|
|
49
53
|
delete payload.seedDataManifest;
|
|
54
|
+
delete payload.platformChannelManifest;
|
|
50
55
|
if (packageArtifact.seedDataManifest !== null) {
|
|
51
56
|
payload.seed_data_manifest = packageArtifact.seedDataManifest;
|
|
52
57
|
}
|
|
58
|
+
if (packageArtifact.platformChannelManifest !== null) {
|
|
59
|
+
payload.platform_channel_manifest = packageArtifact.platformChannelManifest;
|
|
60
|
+
}
|
|
53
61
|
return payload;
|
|
54
62
|
}
|
|
55
63
|
async function buildSourceBundle(root) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-package.js","sourceRoot":"","sources":["../src/runtime-package.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,QAAQ,IAAI,EAAE,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"runtime-package.js","sourceRoot":"","sources":["../src/runtime-package.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -401,6 +401,33 @@ export declare const SchemaStatusSchema: z.ZodObject<{
|
|
|
401
401
|
mapping_id: z.ZodString;
|
|
402
402
|
version: z.ZodLiteral<"OntologyMappingManifestV1">;
|
|
403
403
|
}, z.core.$strict>>;
|
|
404
|
+
platformChannelManifests: z.ZodArray<z.ZodObject<{
|
|
405
|
+
channels: z.ZodArray<z.ZodObject<{
|
|
406
|
+
category: z.ZodUnion<readonly [z.ZodString, z.ZodObject<{
|
|
407
|
+
label: z.ZodOptional<z.ZodString>;
|
|
408
|
+
ordering: z.ZodOptional<z.ZodNumber>;
|
|
409
|
+
slug: z.ZodString;
|
|
410
|
+
}, z.core.$strict>]>;
|
|
411
|
+
category_label: z.ZodOptional<z.ZodString>;
|
|
412
|
+
category_ordering: z.ZodOptional<z.ZodNumber>;
|
|
413
|
+
customer_visibility: z.ZodEnum<{
|
|
414
|
+
hidden: "hidden";
|
|
415
|
+
visible: "visible";
|
|
416
|
+
}>;
|
|
417
|
+
label: z.ZodString;
|
|
418
|
+
landing_agent_ref: z.ZodOptional<z.ZodString>;
|
|
419
|
+
landing_thread_kind: z.ZodOptional<z.ZodEnum<{
|
|
420
|
+
"": "";
|
|
421
|
+
channel: "channel";
|
|
422
|
+
private: "private";
|
|
423
|
+
guest_shared: "guest_shared";
|
|
424
|
+
}>>;
|
|
425
|
+
landing_title: z.ZodOptional<z.ZodString>;
|
|
426
|
+
ordering: z.ZodNumber;
|
|
427
|
+
slug: z.ZodString;
|
|
428
|
+
}, z.core.$strict>>;
|
|
429
|
+
version: z.ZodLiteral<"1">;
|
|
430
|
+
}, z.core.$strict>>;
|
|
404
431
|
projectionContracts: z.ZodArray<z.ZodObject<{
|
|
405
432
|
app_id: z.ZodString;
|
|
406
433
|
contract_id: z.ZodString;
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// MinuteWork substrate. Thin layer — do not put product UI/logic here.
|
|
2
|
-
//
|
|
3
|
-
// Zod schemas for the MinuteWork platform *native* session payloads. These match
|
|
4
|
-
// the SHIPPED platform native-token slice (`/api/v1/native/session/*`) response
|
|
5
|
-
// shapes exactly (DRF emits snake_case). They describe API responses, not product
|
|
6
|
-
// data. Keep them aligned with the platform serializers in
|
|
7
|
-
// `apps/mwv3-platform-dj/apps/gateway_api/serializers.py`:
|
|
8
|
-
// - token pair -> IssuedNativeAppSessionTokenPairSerializer
|
|
9
|
-
// - session/me -> TenantSessionResponseSerializer
|
|
10
|
-
//
|
|
11
|
-
// Schemas use `.passthrough()` so additive platform fields never break parsing;
|
|
12
|
-
// the client only depends on the fields it reads.
|
|
13
|
-
|
|
14
|
-
import { z } from "zod";
|
|
15
|
-
|
|
16
|
-
// A tenant the authenticated user belongs to. Mirrors `_serialize_membership`
|
|
17
|
-
// on the platform; only the load-bearing fields are constrained, the rest pass
|
|
18
|
-
// through. `role` may be an empty string for a membership with no role.
|
|
19
|
-
export const nativeMembershipSchema = z
|
|
20
|
-
.object({
|
|
21
|
-
tenant_id: z.string().min(1),
|
|
22
|
-
tenant_slug: z.string(),
|
|
23
|
-
tenant_name: z.string(),
|
|
24
|
-
role: z.string(),
|
|
25
|
-
})
|
|
26
|
-
.passthrough();
|
|
27
|
-
|
|
28
|
-
export const nativeActiveTenantSchema = nativeMembershipSchema;
|
|
29
|
-
|
|
30
|
-
// Mirrors `_serialize_user`. `email` is not constrained to an email shape because
|
|
31
|
-
// the platform may serialize a blank/non-email value.
|
|
32
|
-
export const nativeUserSchema = z
|
|
33
|
-
.object({
|
|
34
|
-
id: z.string().min(1),
|
|
35
|
-
username: z.string(),
|
|
36
|
-
email: z.string(),
|
|
37
|
-
})
|
|
38
|
-
.passthrough();
|
|
39
|
-
|
|
40
|
-
// Response of `/api/v1/native/session/me/` (and `/context/`):
|
|
41
|
-
// `TenantSessionResponseSerializer`. `active_tenant_id` / `active_tenant` are
|
|
42
|
-
// null when the user has no active membership.
|
|
43
|
-
export const nativeSessionSchema = z
|
|
44
|
-
.object({
|
|
45
|
-
user: nativeUserSchema,
|
|
46
|
-
active_tenant_id: z.string().nullable(),
|
|
47
|
-
active_tenant: nativeActiveTenantSchema.nullable(),
|
|
48
|
-
memberships: z.array(nativeMembershipSchema),
|
|
49
|
-
onboarding_completed_for_active_workspace: z.boolean().nullable().optional(),
|
|
50
|
-
})
|
|
51
|
-
.passthrough();
|
|
52
|
-
|
|
53
|
-
// Platform-issued bearer token pair returned by `/token-exchange/` and
|
|
54
|
-
// `/refresh/`: `IssuedNativeAppSessionTokenPairSerializer`. `*_expires_at` are
|
|
55
|
-
// ISO-8601 timestamps; the client uses the access expiry to refresh proactively.
|
|
56
|
-
export const nativeTokenPairSchema = z
|
|
57
|
-
.object({
|
|
58
|
-
access_token: z.string().min(1),
|
|
59
|
-
refresh_token: z.string().min(1),
|
|
60
|
-
access_token_expires_at: z.string().min(1),
|
|
61
|
-
refresh_token_expires_at: z.string().min(1),
|
|
62
|
-
})
|
|
63
|
-
.passthrough();
|
|
64
|
-
|
|
65
|
-
// What we persist in the device keychain: the two opaque tokens plus the access
|
|
66
|
-
// token's expiry (ISO-8601). This is the on-device shape, distinct from the wire
|
|
67
|
-
// shape above.
|
|
68
|
-
export const storedTokensSchema = z.object({
|
|
69
|
-
access: z.string().min(1),
|
|
70
|
-
refresh: z.string().min(1),
|
|
71
|
-
expiresAt: z.string().min(1),
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
export type NativeMembership = z.infer<typeof nativeMembershipSchema>;
|
|
75
|
-
export type NativeActiveTenant = z.infer<typeof nativeActiveTenantSchema>;
|
|
76
|
-
export type NativeUser = z.infer<typeof nativeUserSchema>;
|
|
77
|
-
export type NativeSession = z.infer<typeof nativeSessionSchema>;
|
|
78
|
-
export type NativeTokenPair = z.infer<typeof nativeTokenPairSchema>;
|
|
79
|
-
export type StoredTokens = z.infer<typeof storedTokensSchema>;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
// MinuteWork substrate. Thin layer — do not put product UI/logic here.
|
|
2
|
-
//
|
|
3
|
-
// Builds absolute URLs for the MinuteWork *platform native* session endpoints.
|
|
4
|
-
//
|
|
5
|
-
// These endpoints are the DIRECT platform API surface for native clients
|
|
6
|
-
// (`/api/v1/native/...`). The mobile app authenticates with a platform-issued
|
|
7
|
-
// bearer token obtained through a browser-assisted device flow, then calls the
|
|
8
|
-
// platform directly. This is intentionally NOT the tenant-app BFF cookie path
|
|
9
|
-
// (the Next.js `platform_session_bff` profile) — there is no per-app server in
|
|
10
|
-
// front of the mobile client.
|
|
11
|
-
//
|
|
12
|
-
// These routes are SHIPPED by the platform native-token slice and are called by
|
|
13
|
-
// the real device-flow client in `src/mw/client.ts`.
|
|
14
|
-
|
|
15
|
-
import { mwEnv } from "@/mw/env";
|
|
16
|
-
|
|
17
|
-
const platformBaseUrl = new URL(
|
|
18
|
-
mwEnv.platformBaseUrl.endsWith("/")
|
|
19
|
-
? mwEnv.platformBaseUrl
|
|
20
|
-
: `${mwEnv.platformBaseUrl}/`,
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
function buildPlatformEndpoint(path: string): string {
|
|
24
|
-
return new URL(path.replace(/^\//, ""), platformBaseUrl).toString();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export const platformNativeEndpoints = {
|
|
28
|
-
// Begin the browser-assisted device authorization flow.
|
|
29
|
-
authorize: buildPlatformEndpoint("/api/v1/native/session/authorize/"),
|
|
30
|
-
// Exchange the authorization result for an access/refresh token pair.
|
|
31
|
-
tokenExchange: buildPlatformEndpoint("/api/v1/native/session/token-exchange/"),
|
|
32
|
-
// Rotate an expired access token using the refresh token.
|
|
33
|
-
refresh: buildPlatformEndpoint("/api/v1/native/session/refresh/"),
|
|
34
|
-
// Resolve the current authenticated principal (user + active tenant).
|
|
35
|
-
me: buildPlatformEndpoint("/api/v1/native/session/me/"),
|
|
36
|
-
// Resolve / switch active tenant context for the session.
|
|
37
|
-
context: buildPlatformEndpoint("/api/v1/native/session/context/"),
|
|
38
|
-
// Revoke the current token pair.
|
|
39
|
-
logout: buildPlatformEndpoint("/api/v1/native/session/logout/"),
|
|
40
|
-
} as const;
|
|
41
|
-
|
|
42
|
-
export type PlatformNativeEndpoint = keyof typeof platformNativeEndpoints;
|