forked from 2sys/phonograph
fix email uniqueness logic for new signups
This commit is contained in:
parent
fc8e3d6b99
commit
b701270d88
5 changed files with 60 additions and 20 deletions
|
|
@ -0,0 +1,2 @@
|
||||||
|
drop index if exists users_email_idx;
|
||||||
|
create index on users (email);
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
drop index if exists users_email_idx;
|
||||||
|
create unique index on users (email);
|
||||||
|
|
@ -110,29 +110,55 @@ returning id, uid, email
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
user
|
user
|
||||||
} else if uid.is_some() {
|
|
||||||
// Conflict should have been on at least `uid`, meaning that the
|
|
||||||
// user already exists and its fields are fully populated.
|
|
||||||
query_as!(User, "select id, uid, email from users where uid = $1", uid)
|
|
||||||
.fetch_one(app_db.get_conn())
|
|
||||||
.await?
|
|
||||||
} else {
|
} else {
|
||||||
// Conflict must have been on `email`, meaning that the user
|
// Conflict may have been on either or both of `email` and
|
||||||
// already exists, though its `uid` may not be up to date. Use
|
// `uid`.
|
||||||
// `COALESCE()` on the `uid` value to ensure that it is not
|
//
|
||||||
// inadvertently removed if present.
|
// If the conflict was on `email`, we want to ensure that `uid`
|
||||||
query_as!(
|
// is updated if and only if it is currently null. This can be
|
||||||
|
// accomplished using `COALESCE()` with the current `uid` value
|
||||||
|
// as the first argument. The parameterized value will only be
|
||||||
|
// used if the current value is null.
|
||||||
|
//
|
||||||
|
// If the conflict was on `uid`, we merely want to return the
|
||||||
|
// record with the matching `uid`.
|
||||||
|
//
|
||||||
|
// If both fields conflicted then the `UPDATE` query will be
|
||||||
|
// sufficient, but if the operation is using a different email
|
||||||
|
// address than the one already stored then we will need one
|
||||||
|
// final `SELECT` query to guarantee that we obtain a result.
|
||||||
|
// Assuming that the caller queries by `uid` prior to starting
|
||||||
|
// the upsert, this last query should only be hit when two HTTP
|
||||||
|
// requests are running the auth flow concurrently.
|
||||||
|
if let Some(user) = query_as!(
|
||||||
User,
|
User,
|
||||||
"
|
"
|
||||||
update users
|
update users
|
||||||
set uid = coalesce($1, uid)
|
set uid = coalesce(uid, $1)
|
||||||
where email = lower($1)
|
where email = lower($2)
|
||||||
returning id, uid, email
|
returning id, uid, email
|
||||||
",
|
",
|
||||||
|
uid,
|
||||||
email,
|
email,
|
||||||
)
|
)
|
||||||
.fetch_one(app_db.get_conn())
|
.fetch_optional(app_db.get_conn())
|
||||||
.await?
|
.await?
|
||||||
|
{
|
||||||
|
user
|
||||||
|
} else {
|
||||||
|
User::with_uid(
|
||||||
|
// TODO: This should hold true *unless* the database is
|
||||||
|
// "tampered with" by an outside actor (such as a
|
||||||
|
// developer performing manual maintenance). While such
|
||||||
|
// an edge case is beyond the scope of the server's
|
||||||
|
// responsibilities, panicking may be considered an
|
||||||
|
// overreaction.
|
||||||
|
uid.expect("uid must be non-null to cause a database conflict"),
|
||||||
|
)
|
||||||
|
.fetch_optional(app_db)
|
||||||
|
.await?
|
||||||
|
.ok_or(sqlx::Error::RowNotFound)?
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ use phono_backends::{escape_identifier, pg_database::PgDatabase, rolnames::ROLE_
|
||||||
use phono_models::{
|
use phono_models::{
|
||||||
accessors::{Accessor as _, Actor, workspace::WorkspaceAccessor},
|
accessors::{Accessor as _, Actor, workspace::WorkspaceAccessor},
|
||||||
user::User,
|
user::User,
|
||||||
|
workspace_user_perm::WorkspaceMembership,
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use sqlx::query;
|
use sqlx::query;
|
||||||
|
|
@ -84,7 +85,7 @@ pub(super) async fn post(
|
||||||
.await?
|
.await?
|
||||||
.datname;
|
.datname;
|
||||||
query(&format!(
|
query(&format!(
|
||||||
"grant connect on database {db_name_esc} to {rolname}",
|
"grant connect on database {db_name_esc} to {rolname} with grant option",
|
||||||
db_name_esc = escape_identifier(&db_name),
|
db_name_esc = escape_identifier(&db_name),
|
||||||
rolname = escape_identifier(&format!(
|
rolname = escape_identifier(&format!(
|
||||||
"{ROLE_PREFIX_USER}{user_id}",
|
"{ROLE_PREFIX_USER}{user_id}",
|
||||||
|
|
@ -93,6 +94,20 @@ pub(super) async fn post(
|
||||||
))
|
))
|
||||||
.execute(root_client.get_conn())
|
.execute(root_client.get_conn())
|
||||||
.await?;
|
.await?;
|
||||||
|
query(&format!(
|
||||||
|
"grant usage, create on schema phono to {rolname} with grant option",
|
||||||
|
rolname = escape_identifier(&format!(
|
||||||
|
"{ROLE_PREFIX_USER}{user_id}",
|
||||||
|
user_id = target_user.id.simple()
|
||||||
|
))
|
||||||
|
))
|
||||||
|
.execute(root_client.get_conn())
|
||||||
|
.await?;
|
||||||
|
WorkspaceMembership::upsert()
|
||||||
|
.workspace_id(workspace_id)
|
||||||
|
.user_id(target_user.id)
|
||||||
|
.execute(&mut app_db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
Ok(navigator
|
Ok(navigator
|
||||||
.workspace_page()
|
.workspace_page()
|
||||||
|
|
|
||||||
|
|
@ -33,10 +33,5 @@
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</menu>
|
</menu>
|
||||||
</section>
|
</section>
|
||||||
<section class="workspace-nav__section">
|
|
||||||
<div class="workspace-nav__heading">
|
|
||||||
<h1>Shared With Me</h1>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</nav>
|
</nav>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue