Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
13 changes: 13 additions & 0 deletions frontend/src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,6 @@ tauri-plugin = "2.1.1"
tauri-plugin-deep-link = "2"
tauri-plugin-opener = "2"
tauri-plugin-os = "2"
tauri-plugin-sign-in-with-apple = "1.0.0"
tokio = { version = "1.0", features = ["time"] }
once_cell = "1.18.0"
32 changes: 32 additions & 0 deletions frontend/src-tauri/apple-sign-in-info.md
Comment thread
AnthonyRonning marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Sign in with Apple Integration for iOS

## Overview
This document provides information about the Sign in with Apple integration for Maple on iOS.

## Configuration
1. The integration uses the tauri-plugin-sign-in-with-apple crate (version 1.0.0)
2. Required entitlements have been added in maple_iOS.entitlements
3. The capability is registered in capabilities/default.json and capabilities/mobile-ios.json

## Frontend Implementation
1. Apple Sign In button is only shown on iOS devices
2. Platform detection via `@tauri-apps/plugin-os`'s `type()` function
3. Authentication is handled through the plugin's command: `plugin:sign-in-with-apple|get_apple_id_credential`

## Future Backend Work
The frontend implementation for Apple Sign In is complete, but the backend integration is not yet implemented.
When the backend is ready, the response from the Apple Sign In should be sent to your backend to:

1. Verify the identity token with Apple
2. Create or update the user account
3. Generate authentication tokens for your app

## Debugging
If you experience issues with Sign in with Apple:
1. Check the debug logs (debug option is enabled)
2. Verify that the entitlements are properly set
3. Ensure your Apple Developer account is configured correctly for Sign in with Apple

## Resources
- [Apple Developer Documentation](https://developer.apple.com/documentation/sign_in_with_apple)
- [tauri-plugin-sign-in-with-apple on crates.io](https://crates.io/crates/tauri-plugin-sign-in-with-apple)
3 changes: 2 additions & 1 deletion frontend/src-tauri/capabilities/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"url": "https://trymaple.ai/*"
}
]
}
},
"sign-in-with-apple:default"
]
}
25 changes: 25 additions & 0 deletions frontend/src-tauri/capabilities/mobile-ios.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"$schema": "../gen/schemas/mobile-schema.json",
"identifier": "mobile-ios",
"description": "Capabilities for iOS",
"windows": [
"main"
],
"platforms": ["iOS"],
"permissions": [
"core:default",
"updater:default",
{
"identifier": "opener:allow-open-url",
"allow": [
{
"url": "http://localhost:5173/*"
},
{
"url": "https://trymaple.ai/*"
}
]
},
"sign-in-with-apple:default"
]
}
4 changes: 4 additions & 0 deletions frontend/src-tauri/gen/apple/maple_iOS/maple_iOS.entitlements
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:trymaple.ai</string>
Expand Down
3 changes: 3 additions & 0 deletions frontend/src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use tauri::Emitter;
use tauri_plugin_deep_link::DeepLinkExt;
use tauri_plugin_opener;
use tauri_plugin_sign_in_with_apple;

// This handles incoming deep links
fn handle_deep_link_event(url: &str, app: &tauri::AppHandle) {
Expand All @@ -21,6 +22,7 @@ pub fn run() {
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_sign_in_with_apple::init())
.setup(|app| {
// Set up the deep link handler
// Use a cloned handle with 'static lifetime
Expand Down Expand Up @@ -193,6 +195,7 @@ pub fn run() {
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_os::init())
.plugin(tauri_plugin_sign_in_with_apple::init())
.setup(|app| {
// Set up the deep link handler for mobile
let app_handle = app.handle().clone();
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/components/icons/Apple.tsx
Comment thread
AnthonyRonning marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from "react";

export function Apple(props: React.SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
{...props}
>
<path d="M12 20.94c1.5 0 2.75 1.06 4 1.06 3 0 6-8 6-12.22A4.91 4.91 0 0 0 17 5c-2.22 0-4 1.44-5 2-1-.56-2.78-2-5-2a4.9 4.9 0 0 0-5 4.78C2 14 5 22 8 22c1.25 0 2.5-1.06 4-1.06Z" />
<path d="M10 2c1 .5 2 2 2 5" />
</svg>
);
}
8 changes: 7 additions & 1 deletion frontend/src/routes/desktop-auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const Route = createFileRoute("/desktop-auth")({
validateSearch: (search: Record<string, unknown>): DesktopAuthSearchParams => {
const provider = typeof search.provider === "string" ? search.provider : "github";
// Validate provider is supported
if (provider !== "github" && provider !== "google") {
if (provider !== "github" && provider !== "google" && provider !== "apple") {
throw new Error(`Unsupported provider: ${provider}`);
}
return {
Expand Down Expand Up @@ -52,6 +52,12 @@ function DesktopAuth() {
} else if (provider === "google") {
const result = await os.initiateGoogleAuth("");
auth_url = result.auth_url;
} else if (provider === "apple") {
Comment thread
AnthonyRonning marked this conversation as resolved.
Outdated
// Apple sign-in is handled natively on iOS, not through browser OAuth
// For this demo, we'll just redirect to login page
console.log("[Apple Auth] Apple Sign In should be handled natively on iOS");
navigate({ to: "/login" });
return;
} else {
Comment thread
AnthonyRonning marked this conversation as resolved.
Outdated
throw new Error("Unsupported provider");
}
Expand Down
66 changes: 65 additions & 1 deletion frontend/src/routes/login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { Label } from "@/components/ui/label";
import { AlertDestructive } from "@/components/AlertDestructive";
import { Loader2, Github, Mail } from "lucide-react";
import { Google } from "@/components/icons/Google";
import { Apple } from "@/components/icons/Apple";
import { AuthMain } from "@/components/AuthMain";
import { isTauri, invoke } from "@tauri-apps/api/core";
import { type } from "@tauri-apps/plugin-os";

Comment thread
AnthonyRonning marked this conversation as resolved.
type LoginSearchParams = {
next?: string;
Expand All @@ -23,7 +25,7 @@ export const Route = createFileRoute("/login")({
})
});

type LoginMethod = "email" | "github" | "google" | null;
type LoginMethod = "email" | "github" | "google" | "apple" | null;

function LoginPage() {
const navigate = useNavigate();
Expand All @@ -32,6 +34,22 @@ function LoginPage() {
const [loginMethod, setLoginMethod] = useState<LoginMethod>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isIOS, setIsIOS] = useState(false);

// Check if running on iOS
useEffect(() => {
const checkPlatform = async () => {
try {
const platform = await type();
setIsIOS(platform === "ios");
} catch (error) {
console.error("Error checking platform:", error);
setIsIOS(false);
}
};

checkPlatform();
}, []);

// Redirect if already logged in
useEffect(() => {
Expand Down Expand Up @@ -150,9 +168,49 @@ function LoginPage() {
}
};

const handleAppleLogin = async () => {
try {
// For Sign in with Apple, we use the Tauri plugin's command
console.log("[OAuth] Initiating Sign in with Apple");

try {
// Invoke the Apple Sign in plugin
// This will show the native Apple authentication UI
const result = await invoke("plugin:sign-in-with-apple|get_apple_id_credential", {
payload: {
scope: ["email", "fullName"],
state: "apple-signin-state",
// Add options to help with debugging
options: {
debug: true
}
}
});

console.log("[OAuth] Apple Sign-In result:", result);

// In a real implementation, you would send this result to your backend
// For now we'll just show a message that this is coming soon
setError("Apple Sign In successful, but backend integration is coming soon.");
Comment thread
AnthonyRonning marked this conversation as resolved.
Outdated
} catch (error) {
console.error("[OAuth] Failed to authenticate with Apple:", error);
// Be more specific about the error message
const errorMessage =
error instanceof Error
? `Apple Sign In error: ${error.message}`
: "Failed to authenticate with Apple. Please try again.";
setError(errorMessage);
}
} catch (error) {
console.error("Failed to initiate Apple login:", error);
setError("Failed to initiate Apple login. Please try again.");
}
};

if (!loginMethod) {
return (
<AuthMain title="Log In" description="Choose your preferred login method">
{error && <AlertDestructive title="Note" description={error} />}
<Button onClick={() => setLoginMethod("email")} className="w-full">
<Mail className="mr-2 h-4 w-4" />
Log in with Email
Expand All @@ -165,6 +223,12 @@ function LoginPage() {
<Google className="mr-2 h-4 w-4" />
Log in with Google
</Button>
{isIOS && (
<Button onClick={handleAppleLogin} className="w-full">
<Apple className="mr-2 h-4 w-4" />
Log in with Apple
</Button>
)}
<div className="text-center text-sm">
Need an account?{" "}
<Link to="/signup" search={next ? { next } : undefined} className="underline">
Expand Down
66 changes: 65 additions & 1 deletion frontend/src/routes/signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import { Label } from "@/components/ui/label";
import { AlertDestructive } from "@/components/AlertDestructive";
import { Loader2, Github, Mail } from "lucide-react";
import { Google } from "@/components/icons/Google";
import { Apple } from "@/components/icons/Apple";
import { AuthMain } from "@/components/AuthMain";
import { isTauri, invoke } from "@tauri-apps/api/core";
import { type } from "@tauri-apps/plugin-os";

Comment thread
AnthonyRonning marked this conversation as resolved.
type SignupSearchParams = {
next?: string;
Expand All @@ -23,7 +25,7 @@ export const Route = createFileRoute("/signup")({
})
});

type SignUpMethod = "email" | "github" | "google" | null;
type SignUpMethod = "email" | "github" | "google" | "apple" | null;

function SignupPage() {
const navigate = useNavigate();
Expand All @@ -32,6 +34,22 @@ function SignupPage() {
const [signUpMethod, setSignUpMethod] = useState<SignUpMethod>(null);
const [error, setError] = useState<string | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [isIOS, setIsIOS] = useState(false);

// Check if running on iOS
useEffect(() => {
const checkPlatform = async () => {
try {
const platform = await type();
setIsIOS(platform === "ios");
} catch (error) {
console.error("Error checking platform:", error);
setIsIOS(false);
}
};

checkPlatform();
}, []);

// Redirect if already logged in
useEffect(() => {
Expand Down Expand Up @@ -150,9 +168,49 @@ function SignupPage() {
}
};

const handleAppleSignup = async () => {
try {
// For Sign in with Apple, we use the Tauri plugin's command
console.log("[OAuth] Initiating Sign in with Apple");

try {
// Invoke the Apple Sign in plugin
// This will show the native Apple authentication UI
const result = await invoke("plugin:sign-in-with-apple|get_apple_id_credential", {
payload: {
scope: ["email", "fullName"],
state: "apple-signup-state",
// Add options to help with debugging
options: {
debug: true
}
Comment thread
AnthonyRonning marked this conversation as resolved.
Outdated
}
});

console.log("[OAuth] Apple Sign-In result:", result);

// In a real implementation, you would send this result to your backend
// For now we'll just show a message that this is coming soon
setError("Apple Sign In successful, but backend integration is coming soon.");
Comment thread
AnthonyRonning marked this conversation as resolved.
Outdated
} catch (error) {
console.error("[OAuth] Failed to authenticate with Apple:", error);
// Be more specific about the error message
const errorMessage =
error instanceof Error
? `Apple Sign In error: ${error.message}`
: "Failed to authenticate with Apple. Please try again.";
setError(errorMessage);
}
} catch (error) {
console.error("Failed to initiate Apple signup:", error);
setError("Failed to initiate Apple signup. Please try again.");
}
};
Comment thread
AnthonyRonning marked this conversation as resolved.
Comment thread
AnthonyRonning marked this conversation as resolved.

if (!signUpMethod) {
return (
<AuthMain title="Sign Up" description="Choose your preferred sign-up method">
{error && <AlertDestructive title="Note" description={error} />}
<Button onClick={() => setSignUpMethod("email")} className="w-full">
<Mail className="mr-2 h-4 w-4" />
Sign up with Email
Expand All @@ -165,6 +223,12 @@ function SignupPage() {
<Google className="mr-2 h-4 w-4" />
Sign up with Google
</Button>
{isIOS && (
<Button onClick={handleAppleSignup} className="w-full">
<Apple className="mr-2 h-4 w-4" />
Sign up with Apple
</Button>
)}
<div className="text-center text-sm">
Already have an account?{" "}
<Link to="/login" search={next ? { next } : undefined} className="underline">
Expand Down