1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! "Sign in with Apple", all batteries included: a button for the web and iOS
//! and the endpoints behind it.
//!
//! ```ignore
//! // Anywhere in a screen:
//! AppleSignIn {}
//! ```
//!
//! ```ignore
//! // Once, in `main`, before the layers (so the session wraps it):
//! let router = dioxus::server::router(App)
//! .merge(g3_auth::apple::apple_router::<AppUser, Client, _>(
//! AppleConfig::new("/home", "/signin/settings"),
//! MyAccounts,
//! ))
//! .layer(..);
//! ```
//!
//! Load [`APPLE_SCHEMA`](crate::APPLE_SCHEMA) with the sessions schema: it holds
//! the one-time challenge each attempt starts with.
//!
//! # How a sign-in is checked
//!
//! 1. The button asks [`APPLE_PREPARE_PATH`] for a single-use `state` and
//! `nonce`, kept in the database for ten minutes. The web page also stores
//! the `state` in an `apple_state` cookie before it leaves.
//! 2. Apple posts the result to [`APPLE_CALLBACK_PATH`] from its own page. The
//! session cookie is `SameSite=Lax` and so is not sent, which means nothing
//! but that cookie and the `state` can tie the post to the browser that
//! started it: [`csrf`](crate::csrf) rejects a browser post whose cookie
//! and `state` differ.
//! 3. The challenge is consumed (a replay finds nothing), and the identity token
//! is verified against Apple's keys: signature, issuer, audience, expiry and
//! the challenge's nonce. With a signing key configured the authorization
//! code is exchanged with Apple too, and the token it returns is the one
//! that counts.
//!
//! # Configuration
//!
//! | Variable | |
//! | --- | --- |
//! | `APPLE_SERVICES_ID` | The web Services ID. Required, at build time for the button and at run time for the server. |
//! | `APP_ORIGIN` | The public origin Apple returns to; its return URL is `<origin>/api/v1/apple_signin_callback`. |
//! | `APPLE_BUNDLE_ID` | The iOS app's bundle id, the audience of native tokens (or [`AppleConfig::native_client_id`]). |
//! | `APPLE_TEAM_ID`, `APPLE_KEY_ID`, `APPLE_PRIVATE_KEY` or `APPLE_PRIVATE_KEY_PATH` | A Sign in with Apple key. Turns on the code exchange and [`revoke_token`], which App Store review requires when an account is deleted. |
use ;
pub use ;
pub use ;
/// Where the button asks for a challenge. Public: it runs before there is a
/// session.
pub const APPLE_PREPARE_PATH: &str = "/api/v1/apple_signin_prepare";
/// Where Apple, and the native flow, post the signed result. Public: the
/// request is how a visitor becomes signed in.
pub const APPLE_CALLBACK_PATH: &str = "/api/v1/apple_signin_callback";
/// What [`APPLE_PREPARE_PATH`] is asked.
/// The single-use challenge the button signs in with.
/// What a native sign-in answers: where the server would have redirected the
/// browser. A web sign-in never sees this, the browser follows the redirect.
/// The Apple Services ID, if one is configured. Compiled into the client
/// (`option_env!`, so it has to be in the environment of `dx build`, not just a
/// `.env` file) and read from the environment at run time on the server.