You've already forked authentication-service
mirror of
https://github.com/matrix-org/matrix-authentication-service.git
synced 2026-01-12 22:51:25 +03:00
Implement proper access token generation
This commit is contained in:
105
Cargo.lock
generated
105
Cargo.lock
generated
@@ -40,9 +40,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alloc-no-stdlib"
|
||||
version = "2.0.1"
|
||||
version = "2.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5192ec435945d87bc2f70992b4d818154b5feede43c09fb7592146374eac90a6"
|
||||
checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3"
|
||||
|
||||
[[package]]
|
||||
name = "alloc-stdlib"
|
||||
@@ -182,9 +182,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
checksum = "2da1976d75adbe5fbc88130ecd119529cf1cc6a93ae1546d8696ee66f0d21af1"
|
||||
|
||||
[[package]]
|
||||
name = "bitvec"
|
||||
@@ -241,9 +241,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli"
|
||||
version = "3.3.0"
|
||||
version = "3.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f29919120f08613aadcd4383764e00526fc9f18b6c0895814faeed0dd78613e"
|
||||
checksum = "71cb90ade945043d3d53597b2fc359bb063db8ade2bcffe7997351d0756e9d50"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -252,9 +252,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "brotli-decompressor"
|
||||
version = "2.3.1"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1052e1c3b8d4d80eb84a8b94f0a1498797b5fb96314c001156a1c761940ef4ec"
|
||||
checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80"
|
||||
dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
"alloc-stdlib",
|
||||
@@ -451,6 +451,21 @@ dependencies = [
|
||||
"build_const",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "10c2722795460108a7872e1cd933a85d6ec38abc4baecad51028f702da28889f"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "1.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccaeedb56da03b09f598226e25e80088cb4cd25f316e6e4df7d695f0feeb1403"
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.2.1"
|
||||
@@ -985,9 +1000,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "0.4.2"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60daa14be0e0786db0f03a9e57cb404c9d756eed2b6c62b9ea98ec5743ec75a9"
|
||||
checksum = "399c583b2979440c60be0821a6199eca73bc3c8dcd9d070d75ac726e2c6186e5"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http",
|
||||
@@ -1140,9 +1155,9 @@ checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
|
||||
|
||||
[[package]]
|
||||
name = "js-sys"
|
||||
version = "0.3.51"
|
||||
version = "0.3.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "83bdfbace3a0e81a4253f73b49e960b053e396a11012cbd49b9b74d6a2b67062"
|
||||
checksum = "ce791b7ca6638aae45be056e068fc756d871eb3b3b10b8efa62d1c9cec616752"
|
||||
dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
@@ -1177,9 +1192,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.98"
|
||||
version = "0.2.99"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "320cfe77175da3a483efed4bc0adc1968ca050b098ce4f2f1c13a56626128790"
|
||||
checksum = "a7f823d141fe0a24df1e23b4af4e3c7ba9e5966ec514ea068c93024aa7deb765"
|
||||
|
||||
[[package]]
|
||||
name = "linked-hash-map"
|
||||
@@ -1222,9 +1237,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "matches"
|
||||
version = "0.1.8"
|
||||
version = "0.1.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "matrix-authentication-service"
|
||||
@@ -1238,6 +1253,7 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"cookie",
|
||||
"crc 2.0.0",
|
||||
"data-encoding",
|
||||
"figment",
|
||||
"headers",
|
||||
@@ -1803,9 +1819,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.2.9"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5ab49abadf3f9e1c4bc499e8845e152ad87d2ad2d30371841171169e9d75feee"
|
||||
checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
@@ -1984,18 +2000,18 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.126"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03"
|
||||
checksum = "f03b9878abf6d14e6779d3f24f07b2cfa90352cfec4acc5aab8f1ac7f146fae8"
|
||||
dependencies = [
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.126"
|
||||
version = "1.0.127"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43"
|
||||
checksum = "a024926d3432516606328597e0f224a51355a493b49fdd67e9209187cbe55ecc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -2120,9 +2136,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.1"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "79c719719ee05df97490f80a45acfc99e5a30ce98a1e4fb67aee422745ae14e3"
|
||||
checksum = "740223c51853f3145fe7c90360d2d4232f2b62e3449489c207eccde818979982"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
@@ -2138,9 +2154,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.3"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
|
||||
checksum = "c307a32c1c5c437f38c7fd45d753050587732ba8628319fbdf12a7e289ccc590"
|
||||
|
||||
[[package]]
|
||||
name = "slug"
|
||||
@@ -2209,7 +2225,7 @@ dependencies = [
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"crc",
|
||||
"crc 1.8.1",
|
||||
"crossbeam-channel 0.5.1",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils 0.8.5",
|
||||
@@ -2556,9 +2572,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
||||
|
||||
[[package]]
|
||||
name = "tokio"
|
||||
version = "1.9.0"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b7b349f11a7047e6d1276853e612d152f5e8a352c61917887cc2169e2366b4c"
|
||||
checksum = "01cf844b23c6131f624accf65ce0e4e9956a8bb329400ea5bcc26ae3a5c20b0b"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"bytes",
|
||||
@@ -2884,12 +2900,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.5"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
|
||||
dependencies = [
|
||||
"matches",
|
||||
]
|
||||
checksum = "246f4c42e67e7a4e3c6106ff716a5d067d4132a642840b242e357e468a2a0085"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
@@ -3047,9 +3060,9 @@ checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.74"
|
||||
version = "0.2.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d54ee1d4ed486f78874278e63e4069fc1ab9f6a18ca492076ffb90c5eb2997fd"
|
||||
checksum = "b608ecc8f4198fe8680e2ed18eccab5f0cd4caaf3d83516fa5fb2e927fda2586"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"wasm-bindgen-macro",
|
||||
@@ -3057,9 +3070,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-backend"
|
||||
version = "0.2.74"
|
||||
version = "0.2.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3b33f6a0694ccfea53d94db8b2ed1c3a8a4c86dd936b13b9f0a15ec4a451b900"
|
||||
checksum = "580aa3a91a63d23aac5b6b267e2d13cb4f363e31dce6c352fca4752ae12e479f"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"lazy_static",
|
||||
@@ -3072,9 +3085,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro"
|
||||
version = "0.2.74"
|
||||
version = "0.2.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "088169ca61430fe1e58b8096c24975251700e7b1f6fd91cc9d59b04fb9b18bd4"
|
||||
checksum = "171ebf0ed9e1458810dfcb31f2e766ad6b3a89dbda42d8901f2b268277e5f09c"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"wasm-bindgen-macro-support",
|
||||
@@ -3082,9 +3095,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-macro-support"
|
||||
version = "0.2.74"
|
||||
version = "0.2.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "be2241542ff3d9f241f5e2cb6dd09b37efe786df8851c54957683a49f0987a97"
|
||||
checksum = "6c2657dd393f03aa2a659c25c6ae18a13a4048cebd220e147933ea837efc589f"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -3095,15 +3108,15 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen-shared"
|
||||
version = "0.2.74"
|
||||
version = "0.2.75"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d7cff876b8f18eed75a66cf49b65e7f967cb354a7aa16003fb55dbfd25b44b4f"
|
||||
checksum = "2e0c4a743a309662d45f4ede961d7afa4ba4131a59a639f29b0069c3798bbcc2"
|
||||
|
||||
[[package]]
|
||||
name = "web-sys"
|
||||
version = "0.3.51"
|
||||
version = "0.3.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e828417b379f3df7111d3a2a9e5753706cae29c41f7c4029ee9fd77f3e09e582"
|
||||
checksum = "01c70a82d842c9979078c772d4a1344685045f1a5628f677c2b2eab4dd7d2696"
|
||||
dependencies = [
|
||||
"js-sys",
|
||||
"wasm-bindgen",
|
||||
|
||||
@@ -7,7 +7,7 @@ license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
# Async runtime
|
||||
tokio = { version = "1.9.0", features = ["full"] }
|
||||
tokio = { version = "1.10.0", features = ["full"] }
|
||||
async-trait = "0.1.51"
|
||||
|
||||
# Logging and tracing
|
||||
@@ -31,7 +31,7 @@ tera = "1.12.1"
|
||||
sqlx = { version = "0.5.5", features = ["runtime-tokio-rustls", "postgres", "migrate", "chrono", "offline"] }
|
||||
|
||||
# Various structure (de)serialization
|
||||
serde = { version = "1.0.126", features = ["derive"] }
|
||||
serde = { version = "1.0.127", features = ["derive"] }
|
||||
serde_yaml = "0.8.17"
|
||||
serde_with = { version = "1.9.4", features = ["hex", "chrono"] }
|
||||
|
||||
@@ -59,3 +59,4 @@ chacha20poly1305 = { version = "0.8.1", features = ["std"] }
|
||||
oauth2-types = { path = "../oauth2-types", features = ["sqlx_type"] }
|
||||
serde_json = "1.0.66"
|
||||
serde_urlencoded = "0.7.0"
|
||||
crc = "2.0.0"
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
-- Copyright 2021 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 oauth2_access_tokens;
|
||||
@@ -0,0 +1,23 @@
|
||||
-- Copyright 2021 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 oauth2_access_tokens (
|
||||
"id" BIGSERIAL PRIMARY KEY,
|
||||
"oauth2_session_id" BIGINT NOT NULL REFERENCES oauth2_sessions (id) ON DELETE CASCADE,
|
||||
|
||||
"token" TEXT UNIQUE NOT NULL,
|
||||
|
||||
"expires_after" INT NOT NULL,
|
||||
"created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
@@ -378,6 +378,52 @@
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"b766b2b41d8770b5bef9928bb3b96abbaf8466b473e12b21f145c015b7cf2f05": {
|
||||
"query": "\n INSERT INTO oauth2_access_tokens\n (oauth2_session_id, token, expires_after)\n VALUES\n ($1, $2, $3)\n RETURNING\n id, oauth2_session_id, token, expires_after, created_at\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "oauth2_session_id",
|
||||
"type_info": "Int8"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "token",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "expires_after",
|
||||
"type_info": "Int4"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "created_at",
|
||||
"type_info": "Timestamptz"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Int8",
|
||||
"Text",
|
||||
"Int4"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"f9a09ff53b6f221649f4f050e3d5ade114f852ddf50a78610a6c0ef0689af681": {
|
||||
"query": "\n INSERT INTO users (username, hashed_password)\n VALUES ($1, $2)\n RETURNING id\n ",
|
||||
"describe": {
|
||||
|
||||
@@ -17,6 +17,7 @@ use std::{
|
||||
convert::TryFrom,
|
||||
};
|
||||
|
||||
use chrono::Duration;
|
||||
use data_encoding::BASE64URL_NOPAD;
|
||||
use hyper::{
|
||||
header::LOCATION,
|
||||
@@ -31,6 +32,7 @@ use oauth2_types::{
|
||||
ResponseType,
|
||||
},
|
||||
};
|
||||
use rand::thread_rng;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{PgPool, Postgres, Transaction};
|
||||
use url::Url;
|
||||
@@ -50,10 +52,11 @@ use crate::{
|
||||
},
|
||||
handlers::views::LoginRequest,
|
||||
storage::{
|
||||
oauth2::{get_session_by_id, start_session},
|
||||
oauth2::{add_access_token, get_session_by_id, start_session},
|
||||
SessionInfo,
|
||||
},
|
||||
templates::{FormPostContext, Templates},
|
||||
tokens,
|
||||
};
|
||||
|
||||
fn back_to_client<T>(
|
||||
@@ -312,10 +315,19 @@ async fn step(
|
||||
|
||||
// Did they request an access token?
|
||||
if response_type.contains(&ResponseType::Token) {
|
||||
// TODO: generate and store an access token
|
||||
params.access_token = Some(AccessTokenResponse::new(
|
||||
"some_static_token_that_should_be_generated".into(),
|
||||
));
|
||||
let ttl = Duration::minutes(5);
|
||||
let (access_token, refresh_token) = {
|
||||
let mut rng = thread_rng();
|
||||
(
|
||||
tokens::generate(&mut rng, tokens::TokenType::AccessToken),
|
||||
tokens::generate(&mut rng, tokens::TokenType::RefreshToken)
|
||||
)
|
||||
};
|
||||
|
||||
add_access_token(&mut txn, oauth2_session_id, &access_token, ttl).await.wrap_error()?;
|
||||
params.access_token = Some(AccessTokenResponse::new(access_token).with_expires_in(ttl));
|
||||
// TODO: save the refresh token
|
||||
params.refresh_token = Some(refresh_token);
|
||||
}
|
||||
|
||||
// Did they request an ID token?
|
||||
|
||||
@@ -30,6 +30,7 @@ mod filters;
|
||||
mod handlers;
|
||||
mod storage;
|
||||
mod templates;
|
||||
mod tokens;
|
||||
|
||||
use self::cli::RootCommand;
|
||||
|
||||
|
||||
@@ -247,3 +247,40 @@ pub async fn add_code(
|
||||
.await
|
||||
.context("could not insert oauth2 authorization code")
|
||||
}
|
||||
|
||||
#[derive(FromRow, Serialize)]
|
||||
pub struct OAuth2AccessToken {
|
||||
id: i64,
|
||||
oauth2_session_id: i64,
|
||||
token: String,
|
||||
expires_after: i32,
|
||||
created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
pub async fn add_access_token(
|
||||
executor: impl Executor<'_, Database = Postgres>,
|
||||
oauth2_session_id: i64,
|
||||
token: &str,
|
||||
expires_after: Duration,
|
||||
) -> anyhow::Result<OAuth2AccessToken> {
|
||||
// Checked convertion of duration to i32, maxing at i32::MAX
|
||||
let expires_after = i32::try_from(expires_after.num_seconds()).unwrap_or(i32::MAX);
|
||||
|
||||
sqlx::query_as!(
|
||||
OAuth2AccessToken,
|
||||
r#"
|
||||
INSERT INTO oauth2_access_tokens
|
||||
(oauth2_session_id, token, expires_after)
|
||||
VALUES
|
||||
($1, $2, $3)
|
||||
RETURNING
|
||||
id, oauth2_session_id, token, expires_after, created_at
|
||||
"#,
|
||||
oauth2_session_id,
|
||||
token,
|
||||
expires_after,
|
||||
)
|
||||
.fetch_one(executor)
|
||||
.await
|
||||
.context("could not insert oauth2 access token")
|
||||
}
|
||||
|
||||
166
matrix-authentication-service/src/tokens.rs
Normal file
166
matrix-authentication-service/src/tokens.rs
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright 2021 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 std::convert::TryInto;
|
||||
|
||||
use crc::{Crc, CRC_32_ISO_HDLC};
|
||||
use rand::{distributions::Alphanumeric, Rng};
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum TokenType {
|
||||
AccessToken,
|
||||
RefreshToken,
|
||||
}
|
||||
|
||||
impl TokenType {
|
||||
fn prefix(self) -> &'static str {
|
||||
match self {
|
||||
TokenType::AccessToken => "mat",
|
||||
TokenType::RefreshToken => "mar",
|
||||
}
|
||||
}
|
||||
|
||||
fn match_prefix(prefix: &str) -> Option<Self> {
|
||||
match prefix {
|
||||
"mat" => Some(TokenType::AccessToken),
|
||||
"mar" => Some(TokenType::RefreshToken),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const NUM: [u8; 62] = *b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
fn base62_encode(mut num: u32) -> String {
|
||||
let mut res = String::with_capacity(6);
|
||||
while num > 0 {
|
||||
res.push(NUM[(num % 62) as usize] as char);
|
||||
num /= 62;
|
||||
}
|
||||
|
||||
format!("{:0>6}", res)
|
||||
}
|
||||
|
||||
const CRC: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
|
||||
|
||||
pub fn generate(rng: impl Rng, token_type: TokenType) -> String {
|
||||
let random_part: String = rng
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(30)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
let base = format!("{}_{}", token_type.prefix(), random_part);
|
||||
let crc = CRC.checksum(base.as_bytes());
|
||||
let crc = base62_encode(crc);
|
||||
format!("{}_{}", base, crc)
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum TokenFormatError {
|
||||
#[error("invalid token format")]
|
||||
InvalidFormat,
|
||||
|
||||
#[error("unknown token prefix {prefix:?}")]
|
||||
UnknownPrefix { prefix: String },
|
||||
|
||||
#[error("invalid crc {got:?}, expected {expected:?}")]
|
||||
InvalidCrc { expected: String, got: String },
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn check(token: &str) -> Result<TokenType, TokenFormatError> {
|
||||
let split: Vec<&str> = token.split('_').collect();
|
||||
let [prefix, random_part, crc]: [&str; 3] = split
|
||||
.try_into()
|
||||
.map_err(|_| TokenFormatError::InvalidFormat)?;
|
||||
|
||||
if prefix.len() != 3 || random_part.len() != 30 || crc.len() != 6 {
|
||||
return Err(TokenFormatError::InvalidFormat);
|
||||
}
|
||||
|
||||
let token_type =
|
||||
TokenType::match_prefix(prefix).ok_or_else(|| TokenFormatError::UnknownPrefix {
|
||||
prefix: prefix.to_string(),
|
||||
})?;
|
||||
|
||||
let base = format!("{}_{}", token_type.prefix(), random_part);
|
||||
let expected_crc = CRC.checksum(base.as_bytes());
|
||||
let expected_crc = base62_encode(expected_crc);
|
||||
if crc != expected_crc {
|
||||
return Err(TokenFormatError::InvalidCrc {
|
||||
expected: expected_crc,
|
||||
got: crc.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Ok(token_type)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use rand::thread_rng;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_prefix_match() {
|
||||
use TokenType::{AccessToken, RefreshToken};
|
||||
assert_eq!(TokenType::match_prefix("mat"), Some(AccessToken));
|
||||
assert_eq!(TokenType::match_prefix("mar"), Some(RefreshToken));
|
||||
assert_eq!(TokenType::match_prefix("matt"), None);
|
||||
assert_eq!(TokenType::match_prefix("marr"), None);
|
||||
assert_eq!(TokenType::match_prefix("ma"), None);
|
||||
assert_eq!(
|
||||
TokenType::match_prefix(TokenType::AccessToken.prefix()),
|
||||
Some(TokenType::AccessToken)
|
||||
);
|
||||
assert_eq!(
|
||||
TokenType::match_prefix(TokenType::RefreshToken.prefix()),
|
||||
Some(TokenType::RefreshToken)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_generate_and_check() {
|
||||
const COUNT: usize = 500; // Generate 500 of each token type
|
||||
let mut rng = thread_rng();
|
||||
// Generate many access tokens
|
||||
let tokens: HashSet<String> = (0..COUNT)
|
||||
.map(|_| generate(&mut rng, TokenType::AccessToken))
|
||||
.collect();
|
||||
|
||||
// Check that they are all different
|
||||
assert_eq!(tokens.len(), COUNT, "All tokens are unique");
|
||||
|
||||
// Check that they are all valid and detected as access tokens
|
||||
for token in tokens {
|
||||
assert_eq!(check(&token).unwrap(), TokenType::AccessToken);
|
||||
}
|
||||
|
||||
// Same, but for refresh tokens
|
||||
let tokens: HashSet<String> = (0..COUNT)
|
||||
.map(|_| generate(&mut rng, TokenType::RefreshToken))
|
||||
.collect();
|
||||
|
||||
assert_eq!(tokens.len(), COUNT, "All tokens are unique");
|
||||
|
||||
for token in tokens {
|
||||
assert_eq!(check(&token).unwrap(), TokenType::RefreshToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,8 @@ license = "Apache-2.0"
|
||||
|
||||
[dependencies]
|
||||
http = "0.2.4"
|
||||
serde = "1.0.126"
|
||||
serde_json = "1.0.64"
|
||||
serde = "1.0.127"
|
||||
serde_json = "1.0.66"
|
||||
language-tags = { version = "0.3.2", features = ["serde"] }
|
||||
url = { version = "2.2.2", features = ["serde"] }
|
||||
parse-display = "0.5.1"
|
||||
|
||||
@@ -156,6 +156,7 @@ pub struct AuthorizationResponse {
|
||||
pub state: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub access_token: Option<AccessTokenResponse>,
|
||||
pub refresh_token: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(
|
||||
|
||||
Reference in New Issue
Block a user