shared/domain/session.rs
1//! types for Auth / session management
2
3use std::fmt;
4
5use super::user::UserId;
6use crate::api::endpoints::PathPart;
7use macros::make_path_parts;
8use serde::{Deserialize, Serialize};
9
10/// The query key for
11pub const AUTH_QUERY_NAME: &str = "access_token";
12
13/// The name to use for auth cookies.
14pub const AUTH_COOKIE_NAME: &str = "X-AUTH";
15
16/// The name of the CSRF header.
17pub const CSRF_HEADER_NAME: &str = "X-CSRF";
18
19make_path_parts!(ImpersonatePath => "/v1/admin/session/user/{}" => UserId);
20
21#[deprecated(note = "use NewSessionResponse")]
22pub use NewSessionResponse as CreateSessionSuccess;
23
24make_path_parts!(CreateSessionPath => "/v1/session");
25
26/// Response for creating a session.
27///
28/// Notes:
29/// * When creating a `Register` session through OAuth, the API also returns user profile info
30/// given as part of the identity claims from the OAuth provider (e.g. Google).
31/// * This response *also* includes a cookie.
32///
33/// Returned cookie auth token can be passed to the API in three ways in requests.
34/// They are listed below in order of precedence (i.e. if 1 exists then 2, 3 are ignored):
35///
36/// 1. Passed as a query `<uri-to-resource>?access_token=<token>`.
37/// 2. Passed in the request header as `Authorization: Bearer <token>`.
38/// 3. As a cookie, `X-AUTH=<token>`. This token will also be authenticated against the CSRF-prevention
39/// header.
40#[derive(Serialize, Deserialize, Debug)]
41#[serde(rename_all = "camelCase")]
42pub enum CreateSessionResponse {
43 /// A new session was successfully created and the user may use the api as normal.
44 Login(NewSessionResponse),
45
46 /// The user has no profile, a token for creating one has been returned.
47 /// * If using OAuth, then a [`Some(OAuthUserProfile)`](OAuthUserProfile) is included as well
48 /// containing known information about the user.
49 /// * If using Basic auth, then the `oauth_profile` field will be `None` and not be serialized.
50 ///
51 /// ## Json response without OAuth profile:
52 /// ```json
53 /// {
54 /// "register": {
55 /// "csrf": <CSRF_TOKEN>,
56 /// }
57 /// }
58 /// ```
59 ///
60 /// ## Json response with OAuth profile:
61 /// ```json
62 /// {
63 /// "register": {
64 /// "csrf": <CSRF_TOKEN>,
65 /// "oauth_profile": {
66 /// "email": <EMAIL>,
67 /// ... # other optional profile fields
68 /// }
69 /// }
70 /// }
71 /// ```
72 Register {
73 /// Csrf token. Note that this field is "flattened" into it's contents when (de)serialized. See example above.
74 #[serde(flatten)]
75 response: NewSessionResponse,
76 /// Oauth profile
77 #[serde(skip_serializing_if = "Option::is_none")]
78 oauth_profile: Option<OAuthUserProfile>,
79 },
80}
81
82/// User's profile info fetched from the OAuth service. Returned as part of the identity claims
83/// to be used as defaults for populating a `PutProfile` request.
84#[derive(Serialize, Deserialize, Debug, Clone)]
85#[serde(rename_all = "camelCase")]
86pub struct OAuthUserProfile {
87 /// The user's email
88 pub email: String,
89
90 /// The user's name
91 #[serde(default)]
92 pub name: Option<String>,
93
94 /// the user's profile picture
95 #[serde(default)]
96 pub profile_picture: Option<String>,
97
98 /// The user's given / first name
99 #[serde(default)]
100 pub given_name: Option<String>,
101
102 /// The user's family / last name
103 #[serde(default)]
104 pub family_name: Option<String>,
105
106 /// The user's locale
107 #[serde(default)]
108 pub locale: Option<String>,
109}
110
111/// Response for successfully creating a session.
112///
113/// Note: This response *also* includes a cookie.
114#[derive(Serialize, Deserialize, Debug)]
115#[serde(rename_all = "camelCase")]
116pub struct NewSessionResponse {
117 /// A transparent CSRF token to use for this Session.
118 pub csrf: String,
119}
120
121/// Which URL to use for OAuth callback.
122#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
123#[serde(rename_all = "camelCase")]
124pub enum OAuthUrlKind {
125 /// Get OAuth Url for login
126 Login,
127 /// Get OAuth Url for register
128 Register,
129}
130impl PathPart for OAuthUrlKind {
131 fn get_path_string(&self) -> String {
132 match self {
133 Self::Login => String::from("login"),
134 Self::Register => String::from("register"),
135 }
136 }
137}
138
139/// Which *service* to use for OAuth Url generation.
140#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
141#[serde(rename_all = "camelCase")]
142#[non_exhaustive]
143pub enum GetOAuthUrlServiceKind {
144 /// Google OAuth v2
145 Google,
146}
147impl PathPart for GetOAuthUrlServiceKind {
148 fn get_path_string(&self) -> String {
149 match self {
150 Self::Google => String::from("google"),
151 }
152 }
153}
154
155/// OAuth provider for emails
156#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
157#[serde(rename_all = "camelCase")]
158pub enum OAuthProvider {
159 /// Google OAuth v2
160 Google,
161}
162
163impl OAuthProvider {
164 #[allow(missing_docs)]
165 pub fn as_str(&self) -> &'static str {
166 match self {
167 Self::Google => "Google",
168 }
169 }
170}
171
172make_path_parts!(GetOAuthPath => "/v1/session/oauth/url/{}/{}" => GetOAuthUrlServiceKind, OAuthUrlKind);
173
174/// Response for what URL to use for OAuth callback.
175#[derive(Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct GetOAuthUrlResponse {
178 /// URL to use for OAuth callback
179 pub url: String,
180}
181
182make_path_parts!(CreateSessionOAuthPath => "/v1/session/oauth");
183
184/// Request for Creating a Session / signing in via oauth.
185#[derive(Serialize, Deserialize)]
186#[serde(rename_all = "camelCase")]
187#[non_exhaustive]
188pub enum CreateSessionOAuthRequest {
189 /// OAuth with google
190 Google {
191 /// The google OAuth Code
192 code: String,
193
194 /// Which OAuth url was used
195 /// Not sure if this is needed anymore
196 redirect_kind: OAuthUrlKind,
197 },
198}
199
200/// Optional query used as the first option for authentication with the API
201#[derive(Serialize, Deserialize)]
202pub struct SessionTokenQuery {
203 /// The token to be used for authentication
204 pub access_token: Option<String>,
205}
206
207impl fmt::Debug for CreateSessionOAuthRequest {
208 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
209 match self {
210 // todo: replace with `finish_non_exhaustive`
211 Self::Google { .. } => f.debug_struct("Google").finish(),
212 }
213 }
214}
215
216make_path_parts!(DeleteSessionPath => "/v1/session");