Skip to main content

arminius_lib_authentication/
test_utils.rs

1// SPDX-FileCopyrightText: 2025 BWI GmbH
2//
3// SPDX-License-Identifier: Apache-2.0
4
5//! Utilities when testing, that allow doing insecure things to
6//! authentication
7
8#![cfg(any(feature = "test_utils", test))]
9
10use arminius_lib_db::{Database, DefaultDb};
11use base64::prelude::{BASE64_URL_SAFE_NO_PAD, Engine as _};
12use color_eyre::eyre::Context as _;
13use jsonwebtoken::jwk::JwkSet;
14use ruma::{DeviceId, owned_device_id};
15use serde_json::json;
16use url::Url;
17use wiremock::{Mock, MockServer, ResponseTemplate};
18
19use super::*;
20
21/// An empty secret, used for authentication
22pub(crate) static SECRET: Vec<u8> = Vec::new();
23
24/// JWK of the in-memory secret used for test authentication
25#[must_use]
26pub fn test_jwk() -> jwk::Jwk {
27	jwk::Jwk {
28		common: jwk::CommonParameters {
29			key_algorithm: jwk::KeyAlgorithm::HS256.into(),
30			..Default::default()
31		},
32		algorithm: jwk::AlgorithmParameters::OctetKey(jwk::OctetKeyParameters {
33			value: BASE64_URL_SAFE_NO_PAD.encode(&SECRET[..]),
34			..Default::default()
35		}),
36	}
37}
38
39/// Generates an auth token for a user and device
40pub fn generate_auth_token(
41	user_id: &UserId,
42	device_id: &DeviceId,
43) -> color_eyre::eyre::Result<String> {
44	jsonwebtoken::encode(
45		&jsonwebtoken::Header::new(jsonwebtoken::Algorithm::HS256),
46		// During testing, users.idp_id is set to user_id,
47		// and devices.idp_id is set to device_id.
48		&Claims {
49			issued_at: 1,
50			subject: user_id.to_string(),
51			session_id: device_id.as_str().to_owned(),
52		},
53		&jsonwebtoken::EncodingKey::from_secret(&SECRET),
54	)
55	.context("Token generation failed")
56}
57
58/// Get a header containing authentication for the user user_id
59pub async fn auth_header_for_new_user(
60	database: &mut DefaultDb,
61	user_id: &OwnedUserId,
62	device_id: &OwnedDeviceId,
63) -> color_eyre::eyre::Result<String> {
64	Ok(format!(
65		"Bearer {}",
66		auth_token_for_new_user_with_device(database, user_id, device_id).await?
67	))
68}
69
70/// Get an auth token for the user user_id with a specific device id
71pub async fn auth_token_for_new_user_with_device(
72	database: &mut impl Database,
73	user_id: &UserId,
74	device_id: &DeviceId,
75) -> color_eyre::eyre::Result<String> {
76	use arminius_lib_db_devices::DeviceQueries as _;
77	database
78		.insert_test_user_with_device(user_id, device_id)
79		.await
80		.context("Failed to insert test user with device")?;
81
82	generate_auth_token(user_id, device_id)
83}
84/// Get an auth token for the user user_id, with the device id MYDEVICEID
85pub async fn auth_token_for_new_user(
86	database: &mut DefaultDb,
87	user_id: &OwnedUserId,
88) -> color_eyre::eyre::Result<String> {
89	auth_token_for_new_user_with_device(database, user_id, &owned_device_id!("MYDEVICEID")).await
90}
91
92/// Mock an OIDC server returning a specific key set and make the server
93/// config point to it.
94pub async fn mock_oidc_keys(keys: Vec<jwk::Jwk>) -> (MockServer, Url) {
95	let mock_server = MockServer::start().await;
96
97	let jwks_path = "/jwks";
98	let well_known_path = "/.well-known/openid-configuration";
99	let well_known_json = json!({
100		"jwks_uri": format!("{}{}", mock_server.uri(), jwks_path)
101	});
102
103	Mock::given(wiremock::matchers::path(jwks_path))
104		.respond_with(ResponseTemplate::new(200).set_body_json(JwkSet { keys }))
105		.mount(&mock_server)
106		.await;
107
108	Mock::given(wiremock::matchers::path(well_known_path))
109		.respond_with(ResponseTemplate::new(200).set_body_json(well_known_json))
110		.mount(&mock_server)
111		.await;
112
113	let Ok(mock_url) = Url::parse(&mock_server.uri()).and_then(|url| url.join(well_known_path))
114	else {
115		unreachable!("mock URL must be valid")
116	};
117	(mock_server, mock_url)
118}