Skip to content

Commit d72bfd8

Browse files
committed
pointercrate-user: Add backend support for oauth registrations
It works pretty much the same as the login/link flow, except the payload now also contains a username to register under. Put it into a different endpoint though, to avoid having to think about weird interactions of "what happens when registering and already being logged in?". Signed-off-by: stadust <43299462+stadust@users.noreply.github.com>
1 parent 0c9edd8 commit d72bfd8

File tree

6 files changed

+91
-13
lines changed

6 files changed

+91
-13
lines changed

pointercrate-core/src/error.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -289,7 +289,7 @@ impl From<sqlx::Error> for CoreError {
289289
*/
290290
if let sqlx::Error::Database(ref err) = error {
291291
if err.kind() == sqlx::error::ErrorKind::UniqueViolation {
292-
return CoreError::Conflict
292+
return CoreError::Conflict;
293293
}
294294
}
295295

pointercrate-user-api/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ pub fn setup(mut rocket: Rocket<Build>) -> Rocket<Build> {
2626
#[cfg(feature = "legacy_accounts")]
2727
page_routes.extend(rocket::routes![pages::register]);
2828
#[cfg(feature = "oauth2")]
29-
auth_routes.extend(rocket::routes![pages::google_oauth_login]);
29+
auth_routes.extend(rocket::routes![pages::google_oauth_login, pages::google_oauth_register]);
3030

3131
#[cfg(feature = "oauth2")]
3232
{

pointercrate-user-api/src/pages.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,17 +16,16 @@ use rocket::{
1616
use std::net::IpAddr;
1717

1818
#[cfg(any(feature = "legacy_accounts", feature = "oauth2"))]
19-
use {pointercrate_core::pool::PointercratePool, rocket::serde::json::Json};
19+
use {pointercrate_core::pool::PointercratePool, pointercrate_user::User, rocket::serde::json::Json};
2020

2121
#[cfg(feature = "legacy_accounts")]
22-
use pointercrate_user::{
23-
auth::legacy::{LegacyAuthenticatedUser, Registration},
24-
User,
25-
};
22+
use pointercrate_user::auth::legacy::{LegacyAuthenticatedUser, Registration};
2623

2724
#[cfg(feature = "oauth2")]
2825
use {
29-
crate::oauth::GoogleCertificateStore, pointercrate_core::error::CoreError, pointercrate_user::auth::oauth::UnvalidatedOauthCredential,
26+
crate::oauth::GoogleCertificateStore,
27+
pointercrate_core::error::CoreError,
28+
pointercrate_user::auth::oauth::{OauthRegistration, UnvalidatedOauthCredential},
3029
};
3130

3231
fn build_cookies(user: &AuthenticatedUser<PasswordOrBrowser>, cookies: &CookieJar<'_>) -> pointercrate_user::error::Result<()> {
@@ -145,3 +144,28 @@ pub async fn google_oauth_login(
145144

146145
Ok(Status::NoContent)
147146
}
147+
148+
#[cfg(feature = "oauth2")]
149+
#[rocket::post("/oauth/google/register", data = "<payload>")]
150+
pub async fn google_oauth_register(
151+
payload: Json<OauthRegistration>, key_store: &State<GoogleCertificateStore>, ip: IpAddr, pool: &State<PointercratePool>,
152+
cookies: &rocket::http::CookieJar<'_>, ratelimits: &State<UserRatelimits>,
153+
) -> pointercrate_core_api::error::Result<Status> {
154+
let OauthRegistration { credential, username } = payload.0;
155+
let validated_credentials = key_store.validate_with_refresh(credential).await.ok_or(CoreError::Unauthorized)?;
156+
157+
let mut connection = pool.transaction().await.map_err(UserError::from)?;
158+
ratelimits.soft_registrations(ip)?;
159+
160+
User::validate_name(&username)?;
161+
162+
let user = AuthenticatedUser::register_oauth(username, validated_credentials, &mut connection).await?;
163+
164+
ratelimits.registrations(ip)?;
165+
166+
connection.commit().await.map_err(UserError::from)?;
167+
168+
build_cookies(&user, cookies)?;
169+
170+
Ok(Status::NoContent)
171+
}

pointercrate-user/src/auth/get.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ impl AuthenticatedUser<NoAuth> {
2525

2626
let auth_type = match row.google_account_id {
2727
Some(_) => AuthenticationType::oauth(user),
28-
None => AuthenticationType::legacy(user, row.password_hash.ok_or_else(|| CoreError::internal_server_error("Non-oauth user without password in database!"))?),
28+
None => AuthenticationType::legacy(
29+
user,
30+
row.password_hash
31+
.ok_or_else(|| CoreError::internal_server_error("Non-oauth user without password in database!"))?,
32+
),
2933
};
3034

3135
Ok(AuthenticatedUser {
@@ -53,7 +57,11 @@ impl AuthenticatedUser<NoAuth> {
5357

5458
let auth_type = match row.google_account_id {
5559
Some(_) => AuthenticationType::oauth(user),
56-
None => AuthenticationType::legacy(user, row.password_hash.ok_or_else(|| CoreError::internal_server_error("Non-oauth user without password in database!"))?),
60+
None => AuthenticationType::legacy(
61+
user,
62+
row.password_hash
63+
.ok_or_else(|| CoreError::internal_server_error("Non-oauth user without password in database!"))?,
64+
),
5765
};
5866

5967
Ok(AuthenticatedUser {

pointercrate-user/src/auth/oauth/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ mod patch;
66
mod post;
77

88
#[cfg(feature = "oauth2")]
9-
pub use post::{GoogleCertificateDatabase, UnvalidatedOauthCredential, ValidatedGoogleCredentials};
9+
pub use post::{GoogleCertificateDatabase, OauthRegistration, UnvalidatedOauthCredential, ValidatedGoogleCredentials};
1010

1111
use crate::User;
1212

pointercrate-user/src/auth/oauth/post.rs

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,25 @@
1+
use crate::auth::{AuthenticatedUser, AuthenticationType, PasswordOrBrowser};
2+
use crate::error::UserError;
3+
use crate::Result;
4+
use crate::{config, User};
15
use chrono::{DateTime, Utc};
26
use jsonwebtoken::{Algorithm, DecodingKey, Validation};
7+
use pointercrate_core::error::CoreError;
38
use serde::Deserialize;
4-
5-
use crate::config;
9+
use sqlx::PgConnection;
610

711
#[derive(Debug, Deserialize)]
812
pub struct UnvalidatedOauthCredential {
913
credential: String,
1014
}
1115

16+
#[derive(Debug, Deserialize)]
17+
pub struct OauthRegistration {
18+
#[serde(flatten)]
19+
pub credential: UnvalidatedOauthCredential,
20+
pub username: String,
21+
}
22+
1223
#[derive(Deserialize)]
1324
pub struct ValidatedGoogleCredentials {
1425
sub: String,
@@ -86,3 +97,38 @@ impl GoogleCertificateDatabase {
8697
.ok()
8798
}
8899
}
100+
101+
impl AuthenticatedUser<PasswordOrBrowser> {
102+
pub async fn register_oauth(username: String, credentials: ValidatedGoogleCredentials, connection: &mut PgConnection) -> Result<Self> {
103+
log::info!("Attempting oauth registration of new user under name {}", username);
104+
105+
match User::by_name(&username, connection).await {
106+
Ok(_) => return Err(UserError::NameTaken),
107+
Err(UserError::UserNotFoundName { .. }) => {},
108+
Err(err) => return Err(err),
109+
}
110+
111+
match AuthenticatedUser::by_validated_google_creds(&credentials, connection).await {
112+
Ok(_) => return Err(CoreError::Unauthorized.into()),
113+
Err(UserError::Core(CoreError::Unauthorized)) => {},
114+
Err(err) => return Err(err),
115+
}
116+
117+
let id = sqlx::query!("INSERT INTO members (name) VALUES ($1) RETURNING member_id", &username)
118+
.fetch_one(connection)
119+
.await?
120+
.member_id;
121+
122+
Ok(AuthenticatedUser {
123+
gen: 0,
124+
auth_type: AuthenticationType::oauth(User {
125+
id,
126+
name: username,
127+
permissions: 0,
128+
display_name: None,
129+
youtube_channel: None,
130+
}),
131+
auth_artifact: PasswordOrBrowser(true),
132+
})
133+
}
134+
}

0 commit comments

Comments
 (0)