Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/sdk-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ jobs:
bun test src/lib/test/integration/apiKeys.test.ts --timeout 30000
bun test src/lib/test/integration/bip85.test.ts --timeout 30000
bun test src/lib/test/integration/developer.test.ts --timeout 30000
bun test src/lib/test/integration/oauthRedirect.test.ts --timeout 30000
bun test src/lib/test/integration/signing.test.ts --timeout 30000
'

Expand Down
12 changes: 12 additions & 0 deletions scripts/ci/check-sdk-package-consumers.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,12 @@ def check(sdk: Path, tarball: Path, consumer: Path) -> None:
'const env: PcrEnvironment = "production";\n'
'const model: Model = { id: "example", created: 0, object: "model", owned_by: "example" };\n'
'type Context = OpenSecretContextType;\n'
'declare const context: Context;\n'
'for (const initiate of [context.initiateGitHubAuth, context.initiateGoogleAuth, '
'context.initiateAppleAuth]) {\n'
' void initiate("");\n'
' void initiate("invite", "https://auth.example.com/callback");\n'
'}\n'
'void [OpenSecretProvider, useOpenSecret, createCustomFetch, env, model];\n'
)
for name in ("consumer.ts", "consumer.mts"):
Expand All @@ -145,6 +151,12 @@ def check(sdk: Path, tarball: Path, consumer: Path) -> None:
'import sdk = require("@mapleai/sdk");\n'
'const env: sdk.PcrEnvironment = "production";\n'
'const model: sdk.Model = { id: "example", created: 0, object: "model", owned_by: "example" };\n'
'declare const context: sdk.OpenSecretContextType;\n'
'for (const initiate of [context.initiateGitHubAuth, context.initiateGoogleAuth, '
'context.initiateAppleAuth]) {\n'
' void initiate("");\n'
' void initiate("invite", "https://auth.example.com/callback");\n'
'}\n'
'void [sdk.OpenSecretProvider, sdk.useOpenSecret, env, model];\n'
)
runtime_exports = [json.loads(run(["node", name], consumer)) for name in (
Expand Down
24 changes: 24 additions & 0 deletions sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,30 @@ conversations, inference, and account operations. Internal developer tooling
uses `OpenSecretDeveloper` and `useOpenSecretDeveloper`; preserve that surface
when changing the public exports.

### OAuth callback selection (4.1.0)

The three browser initiation methods accept an optional final callback URL:

```ts
const os = useOpenSecret();
await os.initiateGoogleAuth(inviteCode); // Existing provider default.
await os.initiateGoogleAuth(inviteCode, `${window.location.origin}/auth/google/callback`);
await os.initiateGitHubAuth(inviteCode, `${window.location.origin}/auth/github/callback`);
await os.initiateAppleAuth(inviteCode, `${window.location.origin}/auth/apple/callback`);
```

The SDK forwards a supplied URL unchanged as `redirect_url`; an omitted
argument keeps the existing request shape. The backend validates exact
membership in that project's provider settings. Its platform settings types
also expose `additional_redirect_urls?: string[] | null`: when the provider
object is supplied, omission or `null` preserves the list and `[]` clears it.
See the [backend contract](../services/opensecret/docs/oauth-callbacks.md).

Deploy backend callback-selection support and register the URL with the
provider before selecting a non-default callback. An older backend ignores
the new field and uses its default; the SDK does not silently retry with a
different callback. Native Apple sign-in and the Rust SDK are unchanged.

### Development

Use the pinned Nix shell and Bun version. `bun.lock` is the supported dependency
Expand Down
2 changes: 1 addition & 1 deletion sdk/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mapleai/sdk",
"version": "4.0.1",
"version": "4.1.0",
"packageManager": "bun@1.3.5",
"license": "MIT",
"homepage": "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/MaplePrivacyLabs/Maple/tree/master/sdk",
Expand Down
37 changes: 28 additions & 9 deletions sdk/src/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -391,13 +391,19 @@ export async function changePassword(currentPassword: string, newPassword: strin

export async function initiateGitHubAuth(
client_id: string,
inviteCode?: string
inviteCode?: string,
redirectUrl?: string
): Promise<GithubAuthResponse> {
const request = {
client_id,
...(inviteCode ? { invite_code: inviteCode } : {}),
...(redirectUrl !== undefined ? { redirect_url: redirectUrl } : {})
};
try {
return await encryptedApiCall<{ invite_code?: string; client_id: string }, GithubAuthResponse>(
return await encryptedApiCall<typeof request, GithubAuthResponse>(
`${apiUrl}/auth/github`,
"POST",
inviteCode ? { invite_code: inviteCode, client_id } : { client_id },
request,
undefined,
"Failed to initiate GitHub auth"
);
Expand Down Expand Up @@ -570,13 +576,19 @@ export type AppleAuthResponse = {

export async function initiateGoogleAuth(
client_id: string,
inviteCode?: string
inviteCode?: string,
redirectUrl?: string
): Promise<GoogleAuthResponse> {
const request = {
client_id,
...(inviteCode ? { invite_code: inviteCode } : {}),
...(redirectUrl !== undefined ? { redirect_url: redirectUrl } : {})
};
try {
return await encryptedApiCall<{ invite_code?: string; client_id: string }, GoogleAuthResponse>(
return await encryptedApiCall<typeof request, GoogleAuthResponse>(
`${apiUrl}/auth/google`,
"POST",
inviteCode ? { invite_code: inviteCode, client_id } : { client_id },
request,
undefined,
"Failed to initiate Google auth"
);
Expand Down Expand Up @@ -637,6 +649,7 @@ export async function handleGoogleCallback(
* Initiates Apple OAuth authentication flow
* @param client_id - The client ID for your OpenSecret project
* @param inviteCode - Optional invite code for new user registration
* @param redirectUrl - Optional exact backend-registered callback URL; omitted uses the project default
* @returns A promise resolving to the Apple auth response containing auth URL and state
* @description
* This function starts the Apple OAuth authentication process by:
Expand All @@ -649,13 +662,19 @@ export async function handleGoogleCallback(
*/
export async function initiateAppleAuth(
client_id: string,
inviteCode?: string
inviteCode?: string,
redirectUrl?: string
): Promise<AppleAuthResponse> {
const request = {
client_id,
...(inviteCode ? { invite_code: inviteCode } : {}),
...(redirectUrl !== undefined ? { redirect_url: redirectUrl } : {})
};
try {
return await encryptedApiCall<{ invite_code?: string; client_id: string }, AppleAuthResponse>(
return await encryptedApiCall<typeof request, AppleAuthResponse>(
`${apiUrl}/auth/apple`,
"POST",
inviteCode ? { invite_code: inviteCode, client_id } : { client_id },
request,
undefined,
"Failed to initiate Apple auth"
);
Expand Down
18 changes: 9 additions & 9 deletions sdk/src/lib/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -234,11 +234,11 @@ export type OpenSecretContextType = {
* 4. After successful deletion, the client should clear all local storage and tokens
*/
confirmAccountDeletion: (confirmationCode: string, plaintextSecret: string) => Promise<void>;
initiateGitHubAuth: (inviteCode: string) => Promise<api.GithubAuthResponse>;
initiateGitHubAuth: (inviteCode: string, redirectUrl?: string) => Promise<api.GithubAuthResponse>;
handleGitHubCallback: (code: string, state: string, inviteCode: string) => Promise<void>;
initiateGoogleAuth: (inviteCode: string) => Promise<api.GoogleAuthResponse>;
initiateGoogleAuth: (inviteCode: string, redirectUrl?: string) => Promise<api.GoogleAuthResponse>;
handleGoogleCallback: (code: string, state: string, inviteCode: string) => Promise<void>;
initiateAppleAuth: (inviteCode: string) => Promise<api.AppleAuthResponse>;
initiateAppleAuth: (inviteCode: string, redirectUrl?: string) => Promise<api.AppleAuthResponse>;
handleAppleCallback: (code: string, state: string, inviteCode: string) => Promise<void>;
handleAppleNativeSignIn: (appleUser: api.AppleUser, inviteCode?: string) => Promise<void>;
mintNativeHandoffGrant: typeof api.mintNativeHandoffGrant;
Expand Down Expand Up @@ -1254,9 +1254,9 @@ export function OpenSecretProvider({
}
}

const initiateGitHubAuth = async (inviteCode: string) => {
const initiateGitHubAuth = async (inviteCode: string, redirectUrl?: string) => {
try {
return await api.initiateGitHubAuth(clientId, inviteCode);
return await api.initiateGitHubAuth(clientId, inviteCode, redirectUrl);
} catch (error) {
console.error("Failed to initiate GitHub auth:", error);
throw error;
Expand All @@ -1275,9 +1275,9 @@ export function OpenSecretProvider({
}
};

const initiateGoogleAuth = async (inviteCode: string) => {
const initiateGoogleAuth = async (inviteCode: string, redirectUrl?: string) => {
try {
return await api.initiateGoogleAuth(clientId, inviteCode);
return await api.initiateGoogleAuth(clientId, inviteCode, redirectUrl);
} catch (error) {
console.error("Failed to initiate Google auth:", error);
throw error;
Expand All @@ -1296,9 +1296,9 @@ export function OpenSecretProvider({
}
};

const initiateAppleAuth = async (inviteCode: string) => {
const initiateAppleAuth = async (inviteCode: string, redirectUrl?: string) => {
try {
return await api.initiateAppleAuth(clientId, inviteCode);
return await api.initiateAppleAuth(clientId, inviteCode, redirectUrl);
} catch (error) {
console.error("Failed to initiate Apple auth:", error);
throw error;
Expand Down
2 changes: 2 additions & 0 deletions sdk/src/lib/platformApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export type EmailSettings = {
export type OAuthProviderSettings = {
client_id: string;
redirect_url: string;
/** Omitted or null preserves the registered list on update; [] clears it. */
additional_redirect_urls?: string[] | null;
team_id?: string; // Apple-specific: The Apple Developer Team ID
key_id?: string; // Apple-specific: The Apple Developer Key ID for Sign in with Apple
};
Expand Down
Loading
Loading