1
0
mirror of https://github.com/matrix-org/matrix-authentication-service.git synced 2025-07-31 09:24:31 +03:00

Handle legacy token expiration & refresh tokens

This commit is contained in:
Quentin Gliech
2022-05-18 16:10:31 +02:00
parent c4fa87e457
commit 309c89fc4f
10 changed files with 682 additions and 199 deletions

View File

@ -12,4 +12,6 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
DROP TABLE compat_refresh_tokens;
DROP TABLE compat_access_tokens;
DROP TABLE compat_session;

View File

@ -12,12 +12,31 @@
-- See the License for the specific language governing permissions and
-- limitations under the License.
CREATE TABLE compat_access_tokens (
CREATE TABLE compat_sessions (
"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
)
);
CREATE TABLE compat_access_tokens (
"id" BIGSERIAL PRIMARY KEY,
"compat_session_id" BIGINT NOT NULL REFERENCES compat_sessions (id) ON DELETE CASCADE,
"token" TEXT UNIQUE NOT NULL,
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"expires_at" TIMESTAMP WITH TIME ZONE
);
CREATE TABLE compat_refresh_tokens (
"id" BIGSERIAL PRIMARY KEY,
"compat_session_id" BIGINT NOT NULL REFERENCES compat_sessions (id) ON DELETE CASCADE,
"compat_access_token_id" BIGINT REFERENCES compat_access_tokens (id) ON DELETE SET NULL,
"token" TEXT UNIQUE NOT NULL,
"next_token_id" BIGINT REFERENCES compat_refresh_tokens (id),
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);

View File

@ -1 +0,0 @@
-- Add down migration script here

View File

@ -1,50 +0,0 @@
-- 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_sessions (
"id" BIGSERIAL PRIMARY KEY,
"user_id" BIGINT NOT NULL REFERENCES users (id) ON DELETE CASCADE,
"device_id" TEXT UNIQUE NOT NULL,
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
"deleted_at" TIMESTAMP WITH TIME ZONE
);
INSERT INTO compat_sessions (user_id, device_id, created_at, deleted_at)
SELECT user_id, device_id, created_at, deleted_at
FROM compat_access_tokens;
ALTER TABLE compat_access_tokens
ADD COLUMN "compat_session_id" BIGINT REFERENCES compat_sessions (id) ON DELETE CASCADE;
UPDATE compat_access_tokens
SET compat_session_id = compat_sessions.id
FROM compat_sessions
WHERE compat_sessions.device_id = compat_access_tokens.device_id;
ALTER TABLE compat_access_tokens
ALTER COLUMN "compat_session_id" SET NOT NULL,
DROP COLUMN "device_id",
DROP COLUMN "user_id",
DROP COLUMN "deleted_at",
ADD COLUMN "expires_after" INT;
CREATE TABLE compat_refresh_tokens (
"id" BIGSERIAL PRIMARY KEY,
"compat_session_id" BIGINT NOT NULL REFERENCES compat_sessions (id) ON DELETE CASCADE,
"compat_access_token_id" BIGINT REFERENCES compat_access_tokens (id) ON DELETE SET NULL,
"token" TEXT UNIQUE NOT NULL,
"next_token_id" BIGINT REFERENCES compat_refresh_tokens (id),
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
);

View File

@ -699,6 +699,104 @@
},
"query": "\n INSERT INTO oauth2_clients\n (client_id,\n encrypted_client_secret,\n response_types,\n grant_type_authorization_code,\n grant_type_refresh_token,\n contacts,\n client_name,\n logo_uri,\n client_uri,\n policy_uri,\n tos_uri,\n jwks_uri,\n jwks,\n id_token_signed_response_alg,\n userinfo_signed_response_alg,\n token_endpoint_auth_method,\n token_endpoint_auth_signing_alg,\n initiate_login_uri)\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18)\n RETURNING id\n "
},
"5ee505120c3bfddccd7c933de356dd035d18d56316ddf4d0be0d13530b8a643c": {
"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_expires_at",
"ordinal": 3,
"type_info": "Timestamptz"
},
{
"name": "compat_session_id",
"ordinal": 4,
"type_info": "Int8"
},
{
"name": "compat_session_created_at",
"ordinal": 5,
"type_info": "Timestamptz"
},
{
"name": "compat_session_deleted_at",
"ordinal": 6,
"type_info": "Timestamptz"
},
{
"name": "compat_session_device_id",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "user_id!",
"ordinal": 8,
"type_info": "Int8"
},
{
"name": "user_username!",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "user_email_id?",
"ordinal": 10,
"type_info": "Int8"
},
{
"name": "user_email?",
"ordinal": 11,
"type_info": "Text"
},
{
"name": "user_email_created_at?",
"ordinal": 12,
"type_info": "Timestamptz"
},
{
"name": "user_email_confirmed_at?",
"ordinal": 13,
"type_info": "Timestamptz"
}
],
"nullable": [
false,
false,
false,
true,
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.expires_at AS \"compat_access_token_expires_at\",\n cs.id AS \"compat_session_id\",\n cs.created_at AS \"compat_session_created_at\",\n cs.deleted_at AS \"compat_session_deleted_at\",\n cs.device_id AS \"compat_session_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 compat_sessions cs\n ON cs.id = ct.compat_session_id\n INNER JOIN users u\n ON u.id = cs.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.expires_at IS NULL OR ct.expires_at > NOW())\n AND cs.deleted_at IS NULL\n "
},
"647a2a5bbde39d0ed3931d0287b468bc7dedf6171e1dc6171a5d9f079b9ed0fa": {
"describe": {
"columns": [
@ -1074,104 +1172,6 @@
},
"query": "\n INSERT INTO oauth2_sessions\n (user_session_id, oauth2_client_id, scope)\n SELECT\n $1,\n og.oauth2_client_id,\n og.scope\n FROM\n oauth2_authorization_grants og\n WHERE\n og.id = $2\n RETURNING id, created_at\n "
},
"7d94b7b6ed2f68479adb6247880b32bc378790174a81a05dff50b92e9be15bf8": {
"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_expires_after",
"ordinal": 3,
"type_info": "Int4"
},
{
"name": "compat_session_id",
"ordinal": 4,
"type_info": "Int8"
},
{
"name": "compat_session_created_at",
"ordinal": 5,
"type_info": "Timestamptz"
},
{
"name": "compat_session_deleted_at",
"ordinal": 6,
"type_info": "Timestamptz"
},
{
"name": "compat_session_device_id",
"ordinal": 7,
"type_info": "Text"
},
{
"name": "user_id!",
"ordinal": 8,
"type_info": "Int8"
},
{
"name": "user_username!",
"ordinal": 9,
"type_info": "Text"
},
{
"name": "user_email_id?",
"ordinal": 10,
"type_info": "Int8"
},
{
"name": "user_email?",
"ordinal": 11,
"type_info": "Text"
},
{
"name": "user_email_created_at?",
"ordinal": 12,
"type_info": "Timestamptz"
},
{
"name": "user_email_confirmed_at?",
"ordinal": 13,
"type_info": "Timestamptz"
}
],
"nullable": [
false,
false,
false,
true,
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.expires_after AS \"compat_access_token_expires_after\",\n cs.id AS \"compat_session_id\",\n cs.created_at AS \"compat_session_created_at\",\n cs.deleted_at AS \"compat_session_deleted_at\",\n cs.device_id AS \"compat_session_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 compat_sessions cs\n ON cs.id = ct.compat_session_id\n INNER JOIN users u\n ON u.id = cs.user_id\n LEFT JOIN user_emails ue\n ON ue.id = u.primary_email_id\n\n WHERE ct.token = $1\n AND cs.deleted_at IS NULL\n "
},
"7de9cfa6e90ba20f5b298ea387cf13a7e40d0f5b3eb903a80d06fbe33074d596": {
"describe": {
"columns": [
@ -1231,6 +1231,34 @@
},
"query": "\n INSERT INTO compat_sessions (user_id, device_id)\n VALUES ($1, $2)\n RETURNING id, created_at\n "
},
"8aed8f0b7aec4854f8dfc88f43e3e6029ef563189eff6ed1e33c3421b395040c": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int8"
},
{
"name": "created_at",
"ordinal": 1,
"type_info": "Timestamptz"
}
],
"nullable": [
false,
false
],
"parameters": {
"Left": [
"Int8",
"Text",
"Interval"
]
}
},
"query": "\n INSERT INTO compat_access_tokens (compat_session_id, token, created_at, expires_at)\n VALUES ($1, $2, NOW(), NOW() + $3)\n RETURNING id, created_at\n "
},
"9882e49f34dff80c1442565f035a1b47ed4dbae1a405f58cf2db198885bb9f47": {
"describe": {
"columns": [
@ -1803,6 +1831,34 @@
},
"query": "\n SELECT \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 FROM user_emails ue\n\n WHERE ue.user_id = $1\n AND ue.email = $2\n "
},
"dbf9d2ee583d4dec07d7948c7540ff39b3e1de0c6abd168f47c02401f8417eec": {
"describe": {
"columns": [
{
"name": "id",
"ordinal": 0,
"type_info": "Int8"
},
{
"name": "created_at",
"ordinal": 1,
"type_info": "Timestamptz"
}
],
"nullable": [
false,
false
],
"parameters": {
"Left": [
"Int8",
"Int8",
"Text"
]
}
},
"query": "\n INSERT INTO compat_refresh_tokens (compat_session_id, compat_access_token_id, token)\n VALUES ($1, $2, $3)\n RETURNING id, created_at\n "
},
"dda03ba41249bff965cb8f129acc15f4e40807adb9b75dee0ac43edd7809de84": {
"describe": {
"columns": [
@ -1982,5 +2038,97 @@
}
},
"query": "TRUNCATE oauth2_client_redirect_uris, oauth2_clients RESTART IDENTITY CASCADE"
},
"fc5d32bab9999ad383f906dbf20a45dafba1149e809155eccb4d94506ff6cf6f": {
"describe": {
"columns": [
{
"name": "compat_refresh_token_id",
"ordinal": 0,
"type_info": "Int8"
},
{
"name": "compat_refresh_token",
"ordinal": 1,
"type_info": "Text"
},
{
"name": "compat_refresh_token_created_at",
"ordinal": 2,
"type_info": "Timestamptz"
},
{
"name": "compat_session_id",
"ordinal": 3,
"type_info": "Int8"
},
{
"name": "compat_session_created_at",
"ordinal": 4,
"type_info": "Timestamptz"
},
{
"name": "compat_session_deleted_at",
"ordinal": 5,
"type_info": "Timestamptz"
},
{
"name": "compat_session_device_id",
"ordinal": 6,
"type_info": "Text"
},
{
"name": "user_id!",
"ordinal": 7,
"type_info": "Int8"
},
{
"name": "user_username!",
"ordinal": 8,
"type_info": "Text"
},
{
"name": "user_email_id?",
"ordinal": 9,
"type_info": "Int8"
},
{
"name": "user_email?",
"ordinal": 10,
"type_info": "Text"
},
{
"name": "user_email_created_at?",
"ordinal": 11,
"type_info": "Timestamptz"
},
{
"name": "user_email_confirmed_at?",
"ordinal": 12,
"type_info": "Timestamptz"
}
],
"nullable": [
false,
false,
false,
false,
false,
true,
false,
false,
false,
false,
false,
false,
true
],
"parameters": {
"Left": [
"Text"
]
}
},
"query": "\n SELECT\n cr.id AS \"compat_refresh_token_id\",\n cr.token AS \"compat_refresh_token\",\n cr.created_at AS \"compat_refresh_token_created_at\",\n cs.id AS \"compat_session_id\",\n cs.created_at AS \"compat_session_created_at\",\n cs.deleted_at AS \"compat_session_deleted_at\",\n cs.device_id AS \"compat_session_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_refresh_tokens cr\n INNER JOIN compat_sessions cs\n ON cs.id = cr.compat_session_id\n INNER JOIN users u\n ON u.id = cs.user_id\n LEFT JOIN user_emails ue\n ON ue.id = u.primary_email_id\n\n WHERE cr.token = $1\n AND cs.deleted_at IS NULL\n "
}
}

View File

@ -15,8 +15,10 @@
use anyhow::Context;
use argon2::{Argon2, PasswordHash};
use chrono::{DateTime, Duration, Utc};
use mas_data_model::{CompatAccessToken, CompatSession, Device, User, UserEmail};
use sqlx::{Acquire, PgExecutor, Postgres};
use mas_data_model::{
CompatAccessToken, CompatRefreshToken, CompatSession, Device, User, UserEmail,
};
use sqlx::{postgres::types::PgInterval, Acquire, PgExecutor, Postgres};
use thiserror::Error;
use tokio::task;
use tracing::{info_span, Instrument};
@ -28,8 +30,8 @@ use crate::{
pub struct CompatAccessTokenLookup {
compat_access_token_id: i64,
compat_access_token: String,
compat_access_token_expires_after: Option<i32>,
compat_access_token_created_at: DateTime<Utc>,
compat_access_token_expires_at: Option<DateTime<Utc>>,
compat_session_id: i64,
compat_session_created_at: DateTime<Utc>,
compat_session_deleted_at: Option<DateTime<Utc>>,
@ -56,7 +58,7 @@ impl CompatAccessTokenLookupError {
}
}
#[tracing::instrument(skip(executor), err)]
#[tracing::instrument(skip_all, err)]
pub async fn lookup_active_compat_access_token(
executor: impl PgExecutor<'_>,
token: &str,
@ -74,7 +76,7 @@ pub async fn lookup_active_compat_access_token(
ct.id AS "compat_access_token_id",
ct.token AS "compat_access_token",
ct.created_at AS "compat_access_token_created_at",
ct.expires_after AS "compat_access_token_expires_after",
ct.expires_at AS "compat_access_token_expires_at",
cs.id AS "compat_session_id",
cs.created_at AS "compat_session_created_at",
cs.deleted_at AS "compat_session_deleted_at",
@ -95,6 +97,7 @@ pub async fn lookup_active_compat_access_token(
ON ue.id = u.primary_email_id
WHERE ct.token = $1
AND (ct.expires_at IS NULL OR ct.expires_at > NOW())
AND cs.deleted_at IS NULL
"#,
token,
@ -107,9 +110,7 @@ pub async fn lookup_active_compat_access_token(
data: res.compat_access_token_id,
token: res.compat_access_token,
created_at: res.compat_access_token_created_at,
expires_after: res
.compat_access_token_expires_after
.map(|d| Duration::seconds(d.into())),
expires_at: res.compat_access_token_expires_at,
};
let primary_email = match (
@ -148,20 +149,131 @@ pub async fn lookup_active_compat_access_token(
Ok((token, session))
}
#[tracing::instrument(skip(conn, password, token), err)]
pub struct CompatRefreshTokenLookup {
compat_refresh_token_id: i64,
compat_refresh_token: String,
compat_refresh_token_created_at: DateTime<Utc>,
compat_session_id: i64,
compat_session_created_at: DateTime<Utc>,
compat_session_deleted_at: Option<DateTime<Utc>>,
compat_session_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 refresh token")]
pub enum CompatRefreshTokenLookupError {
Database(#[from] sqlx::Error),
Inconsistency(#[from] DatabaseInconsistencyError),
}
impl CompatRefreshTokenLookupError {
#[must_use]
pub fn not_found(&self) -> bool {
matches!(self, Self::Database(sqlx::Error::RowNotFound))
}
}
#[tracing::instrument(skip_all, err)]
pub async fn lookup_active_compat_refresh_token(
executor: impl PgExecutor<'_>,
token: &str,
) -> Result<
(
CompatRefreshToken<PostgresqlBackend>,
CompatSession<PostgresqlBackend>,
),
CompatRefreshTokenLookupError,
> {
let res = sqlx::query_as!(
CompatRefreshTokenLookup,
r#"
SELECT
cr.id AS "compat_refresh_token_id",
cr.token AS "compat_refresh_token",
cr.created_at AS "compat_refresh_token_created_at",
cs.id AS "compat_session_id",
cs.created_at AS "compat_session_created_at",
cs.deleted_at AS "compat_session_deleted_at",
cs.device_id AS "compat_session_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_refresh_tokens cr
INNER JOIN compat_sessions cs
ON cs.id = cr.compat_session_id
INNER JOIN users u
ON u.id = cs.user_id
LEFT JOIN user_emails ue
ON ue.id = u.primary_email_id
WHERE cr.token = $1
AND cs.deleted_at IS NULL
"#,
token,
)
.fetch_one(executor)
.instrument(info_span!("Fetch compat refresh token"))
.await?;
let token = CompatRefreshToken {
data: res.compat_refresh_token_id,
token: res.compat_refresh_token,
created_at: res.compat_refresh_token_created_at,
};
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,
};
let device = Device::try_from(res.compat_session_device_id).unwrap();
let session = CompatSession {
data: res.compat_session_id,
user,
device,
created_at: res.compat_session_created_at,
deleted_at: res.compat_session_deleted_at,
};
Ok((token, session))
}
#[tracing::instrument(skip(conn, password), err)]
pub async fn compat_login(
conn: impl Acquire<'_, Database = Postgres>,
username: &str,
password: &str,
device: Device,
token: String,
) -> Result<
(
CompatAccessToken<PostgresqlBackend>,
CompatSession<PostgresqlBackend>,
),
anyhow::Error,
> {
) -> Result<CompatSession<PostgresqlBackend>, anyhow::Error> {
let mut txn = conn.begin().await.context("could not start transaction")?;
// First, lookup the user
@ -216,30 +328,97 @@ pub async fn compat_login(
deleted_at: None,
};
let res = sqlx::query_as!(
IdAndCreationTime,
r#"
txn.commit().await.context("could not commit transaction")?;
Ok(session)
}
#[tracing::instrument(skip(executor, token), err)]
pub async fn add_compat_access_token(
executor: impl PgExecutor<'_>,
session: &CompatSession<PostgresqlBackend>,
token: String,
expires_after: Option<Duration>,
) -> Result<CompatAccessToken<PostgresqlBackend>, anyhow::Error> {
if let Some(expires_after) = expires_after {
// For some reason, we need to convert the type first
let pg_expires_after = PgInterval::try_from(expires_after)
// For some reason, this error type does not let me to just bubble up the error here
.map_err(|e| anyhow::anyhow!("failed to encode duration: {}", e))?;
let res = sqlx::query_as!(
IdAndCreationTime,
r#"
INSERT INTO compat_access_tokens (compat_session_id, token, created_at, expires_at)
VALUES ($1, $2, NOW(), NOW() + $3)
RETURNING id, created_at
"#,
session.data,
token,
pg_expires_after,
)
.fetch_one(executor)
.instrument(tracing::info_span!("Insert compat access token"))
.await
.context("could not insert compat access token")?;
Ok(CompatAccessToken {
data: res.id,
token,
created_at: res.created_at,
expires_at: Some(res.created_at + expires_after),
})
} else {
let res = sqlx::query_as!(
IdAndCreationTime,
r#"
INSERT INTO compat_access_tokens (compat_session_id, token)
VALUES ($1, $2)
RETURNING id, created_at
"#,
session.data,
token,
)
.fetch_one(executor)
.instrument(tracing::info_span!("Insert compat access token"))
.await
.context("could not insert compat access token")?;
Ok(CompatAccessToken {
data: res.id,
token,
created_at: res.created_at,
expires_at: None,
})
}
}
pub async fn add_compat_refresh_token(
executor: impl PgExecutor<'_>,
session: &CompatSession<PostgresqlBackend>,
access_token: &CompatAccessToken<PostgresqlBackend>,
token: String,
) -> Result<CompatRefreshToken<PostgresqlBackend>, anyhow::Error> {
let res = sqlx::query_as!(
IdAndCreationTime,
r#"
INSERT INTO compat_refresh_tokens (compat_session_id, compat_access_token_id, token)
VALUES ($1, $2, $3)
RETURNING id, created_at
"#,
session.data,
access_token.data,
token,
)
.fetch_one(&mut txn)
.instrument(tracing::info_span!("Insert compat access token"))
.fetch_one(executor)
.instrument(tracing::info_span!("Insert compat refresh token"))
.await
.context("could not insert compat access token")?;
.context("could not insert compat refresh token")?;
let token = CompatAccessToken {
Ok(CompatRefreshToken {
data: res.id,
token,
created_at: res.created_at,
expires_after: None,
};
txn.commit().await.context("could not commit transaction")?;
Ok((token, session))
})
}
#[tracing::instrument(skip_all, err)]