1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-29 22:01:14 +03:00

Working legacy login endpoint

This commit is contained in:
Quentin Gliech
2022-05-12 18:47:13 +02:00
parent 1ebdd0b731
commit 1aff98bdb3
14 changed files with 615 additions and 25 deletions

View File

@ -0,0 +1,15 @@
-- Copyright 2022 The Matrix.org Foundation C.I.C.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
DROP TABLE compat_access_tokens;

View File

@ -0,0 +1,23 @@
-- Copyright 2022 The Matrix.org Foundation C.I.C.
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
CREATE TABLE compat_access_tokens (
"id" BIGSERIAL PRIMARY KEY,
"user_id" BIGINT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"token" TEXT UNIQUE NOT NULL,
"device_id" TEXT UNIQUE NOT NULL,
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"deleted_at" TIMESTAMP WITH TIME ZONE
)

View File

@ -1331,6 +1331,34 @@
},
"query": "UPDATE user_sessions SET active = FALSE WHERE id = $1"
},
"a741cb29c617fb2df97f9e4bc84dbf85ff3c02ac87066770291d7bc153451695": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int8"
},
{
"name": "created_at",
"ordinal": 1,
"type_info": "Timestamptz"
}
],
"nullable": [
false,
false
],
"parameters": {
"Left": [
"Int8",
"Text",
"Text"
]
}
},
"query": "\n INSERT INTO compat_access_tokens (user_id, token, device_id)\n VALUES ($1, $2, $3)\n RETURNING id, created_at\n "
},
"a80c14ba82cfc29493048d9e9578ec5ca482c9228efc7c7212dae4fed86b8367": {
"describe": {
"columns": [],
@ -1659,6 +1687,86 @@
},
"query": "\n INSERT INTO users (username)\n VALUES ($1)\n RETURNING id\n "
},
"de1ed5db37e48382a38075247c001cf4d01b7eeff165aa3aa62bb21a3284d5b7": {
"describe": {
"columns": [
{
"name": "compat_access_token_id",
"ordinal": 0,
"type_info": "Int8"
},
{
"name": "compat_access_token",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "compat_access_token_created_at",
"ordinal": 2,
"type_info": "Timestamptz"
},
{
"name": "compat_access_token_deleted_at",
"ordinal": 3,
"type_info": "Timestamptz"
},
{
"name": "compat_access_token_device_id",
"ordinal": 4,
"type_info": "Text"
},
{
"name": "user_id!",
"ordinal": 5,
"type_info": "Int8"
},
{
"name": "user_username!",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "user_email_id?",
"ordinal": 7,
"type_info": "Int8"
},
{
"name": "user_email?",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "user_email_created_at?",
"ordinal": 9,
"type_info": "Timestamptz"
},
{
"name": "user_email_confirmed_at?",
"ordinal": 10,
"type_info": "Timestamptz"
}
],
"nullable": [
false,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "\n SELECT\n ct.id AS \"compat_access_token_id\",\n ct.token AS \"compat_access_token\",\n ct.created_at AS \"compat_access_token_created_at\",\n ct.deleted_at AS \"compat_access_token_deleted_at\",\n ct.device_id AS \"compat_access_token_device_id\",\n u.id AS \"user_id!\",\n u.username AS \"user_username!\",\n ue.id AS \"user_email_id?\",\n ue.email AS \"user_email?\",\n ue.created_at AS \"user_email_created_at?\",\n ue.confirmed_at AS \"user_email_confirmed_at?\"\n\n FROM compat_access_tokens ct\n INNER JOIN users u\n ON u.id = ct.user_id\n LEFT JOIN user_emails ue\n ON ue.id = u.primary_email_id\n\n WHERE ct.token = $1\n AND ct.deleted_at IS NULL\n "
},
"df38de13e2f345175f9ef46b4ae2a4f6637dbf74bb28559da8f4d8969f411d14": {
"describe": {
"columns": [

View File

@ -0,0 +1,202 @@
// Copyright 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use anyhow::Context;
use argon2::{Argon2, PasswordHash};
use chrono::{DateTime, Utc};
use mas_data_model::{CompatAccessToken, User, UserEmail};
use sqlx::{Acquire, PgExecutor, Postgres};
use thiserror::Error;
use tokio::task;
use tracing::{info_span, Instrument};
use crate::{
user::lookup_user_by_username, DatabaseInconsistencyError, IdAndCreationTime, PostgresqlBackend,
};
pub struct CompatAccessTokenLookup {
compat_access_token_id: i64,
compat_access_token: String,
compat_access_token_created_at: DateTime<Utc>,
compat_access_token_deleted_at: Option<DateTime<Utc>>,
compat_access_token_device_id: String,
user_id: i64,
user_username: String,
user_email_id: Option<i64>,
user_email: Option<String>,
user_email_created_at: Option<DateTime<Utc>>,
user_email_confirmed_at: Option<DateTime<Utc>>,
}
#[derive(Debug, Error)]
#[error("failed to lookup compat access token")]
pub enum CompatAccessTokenLookupError {
Database(#[from] sqlx::Error),
Inconsistency(#[from] DatabaseInconsistencyError),
}
impl CompatAccessTokenLookupError {
#[must_use]
pub fn not_found(&self) -> bool {
matches!(self, Self::Database(sqlx::Error::RowNotFound))
}
}
#[tracing::instrument(skip(executor))]
pub async fn lookup_active_compat_access_token(
executor: impl PgExecutor<'_>,
token: &str,
) -> Result<
(
CompatAccessToken<PostgresqlBackend>,
User<PostgresqlBackend>,
),
CompatAccessTokenLookupError,
> {
let res = sqlx::query_as!(
CompatAccessTokenLookup,
r#"
SELECT
ct.id AS "compat_access_token_id",
ct.token AS "compat_access_token",
ct.created_at AS "compat_access_token_created_at",
ct.deleted_at AS "compat_access_token_deleted_at",
ct.device_id AS "compat_access_token_device_id",
u.id AS "user_id!",
u.username AS "user_username!",
ue.id AS "user_email_id?",
ue.email AS "user_email?",
ue.created_at AS "user_email_created_at?",
ue.confirmed_at AS "user_email_confirmed_at?"
FROM compat_access_tokens ct
INNER JOIN users u
ON u.id = ct.user_id
LEFT JOIN user_emails ue
ON ue.id = u.primary_email_id
WHERE ct.token = $1
AND ct.deleted_at IS NULL
"#,
token,
)
.fetch_one(executor)
.instrument(info_span!("Fetch compat access token"))
.await?;
let token = CompatAccessToken {
data: res.compat_access_token_id,
token: res.compat_access_token,
created_at: res.compat_access_token_created_at,
deleted_at: res.compat_access_token_deleted_at,
device_id: res.compat_access_token_device_id,
};
let primary_email = match (
res.user_email_id,
res.user_email,
res.user_email_created_at,
res.user_email_confirmed_at,
) {
(Some(id), Some(email), Some(created_at), confirmed_at) => Some(UserEmail {
data: id,
email,
created_at,
confirmed_at,
}),
(None, None, None, None) => None,
_ => return Err(DatabaseInconsistencyError.into()),
};
let user = User {
data: res.user_id,
username: res.user_username,
sub: format!("fake-sub-{}", res.user_id),
primary_email,
};
Ok((token, user))
}
#[tracing::instrument(skip(conn, password))]
pub async fn compat_login(
conn: impl Acquire<'_, Database = Postgres>,
username: &str,
password: &str,
device_id: String,
token: String,
) -> Result<
(
CompatAccessToken<PostgresqlBackend>,
User<PostgresqlBackend>,
),
anyhow::Error,
> {
let mut txn = conn.begin().await.context("could not start transaction")?;
// First, lookup the user
let user = lookup_user_by_username(&mut txn, username).await?;
// Now, fetch the hashed password from the user associated with that session
let hashed_password: String = sqlx::query_scalar!(
r#"
SELECT up.hashed_password
FROM user_passwords up
WHERE up.user_id = $1
ORDER BY up.created_at DESC
LIMIT 1
"#,
user.data,
)
.fetch_one(&mut txn)
.instrument(tracing::info_span!("Lookup hashed password"))
.await?;
// TODO: pass verifiers list as parameter
// Verify the password in a blocking thread to avoid blocking the async executor
let password = password.to_string();
task::spawn_blocking(move || {
let context = Argon2::default();
let hasher = PasswordHash::new(&hashed_password)?;
hasher.verify_password(&[&context], &password)
})
.instrument(tracing::info_span!("Verify hashed password"))
.await??;
let res = sqlx::query_as!(
IdAndCreationTime,
r#"
INSERT INTO compat_access_tokens (user_id, token, device_id)
VALUES ($1, $2, $3)
RETURNING id, created_at
"#,
user.data,
token,
device_id,
)
.fetch_one(&mut txn)
.await
.context("could not insert compat access token")?;
let token = CompatAccessToken {
data: res.id,
token,
created_at: res.created_at,
deleted_at: None,
device_id,
};
txn.commit().await.context("could not commit transaction")?;
Ok((token, user))
}

View File

@ -1,4 +1,4 @@
// Copyright 2021 The Matrix.org Foundation C.I.C.
// Copyright 2021, 2022 The Matrix.org Foundation C.I.C.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
@ -42,6 +42,7 @@ impl StorageBackend for PostgresqlBackend {
type AuthorizationGrantData = i64;
type BrowserSessionData = i64;
type ClientData = i64;
type CompatAccessTokenData = i64;
type RefreshTokenData = i64;
type SessionData = i64;
type UserData = i64;
@ -56,6 +57,7 @@ struct IdAndCreationTime {
created_at: DateTime<Utc>,
}
pub mod compat;
pub mod oauth2;
pub mod user;