1#![allow(clippy::redundant_pub_crate)]
2
3use std::fmt::Write as _;
4#[cfg(feature = "http-axum")]
5use std::net::{IpAddr, SocketAddr};
6use std::path::Path;
7#[cfg(feature = "http-axum")]
8use std::time::Duration;
9
10#[cfg(feature = "http-axum")]
11use base64::Engine;
12#[cfg(feature = "http-axum")]
13use base64::engine::general_purpose::URL_SAFE_NO_PAD;
14use sha2::{Digest, Sha256};
15
16use crate::error::AuthError;
17
18#[cfg(feature = "http-axum")]
22pub(crate) fn remote_ip(addr: SocketAddr) -> IpAddr {
23 match addr.ip() {
24 IpAddr::V6(v6) => v6
25 .to_ipv4_mapped()
26 .map(IpAddr::V4)
27 .unwrap_or(IpAddr::V6(v6)),
28 v4 => v4,
29 }
30}
31
32pub fn now_unix() -> i64 {
33 let secs = std::time::SystemTime::now()
34 .duration_since(std::time::UNIX_EPOCH)
35 .unwrap_or_default()
36 .as_secs();
37 i64::try_from(secs).unwrap_or(i64::MAX)
38}
39
40#[cfg(feature = "http-axum")]
41pub(crate) fn random_token(bytes: usize) -> Result<String, AuthError> {
42 let mut buf = vec![0_u8; bytes];
43 getrandom::fill(&mut buf)
44 .map_err(|error| AuthError::Storage(format!("generate random token: {error}")))?;
45 Ok(URL_SAFE_NO_PAD.encode(buf))
46}
47
48pub fn fingerprint(value: &str) -> String {
49 let digest = Sha256::digest(value.as_bytes());
50 let mut output = String::with_capacity(12);
51 for byte in &digest[..6] {
52 let _ = write!(&mut output, "{byte:02x}");
53 }
54 output
55}
56
57#[cfg(unix)]
58pub(crate) fn ensure_restrictive_permissions(path: &Path) -> Result<(), AuthError> {
59 use std::os::unix::fs::PermissionsExt;
60
61 let metadata = std::fs::metadata(path)
62 .map_err(|error| AuthError::Storage(format!("stat `{}`: {error}", path.display())))?;
63 let mode = metadata.permissions().mode() & 0o777;
64 if mode & 0o077 != 0 {
65 return Err(AuthError::InsecurePermissions {
66 path: path.to_path_buf(),
67 });
68 }
69 Ok(())
70}
71
72#[cfg(not(unix))]
73pub(crate) fn ensure_restrictive_permissions(_path: &Path) -> Result<(), AuthError> {
74 Ok(())
75}
76
77#[cfg(unix)]
78pub(crate) fn set_restrictive_permissions(path: &Path) -> Result<(), AuthError> {
79 use std::os::unix::fs::PermissionsExt;
80
81 std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))
82 .map_err(|error| AuthError::Storage(format!("chmod 0600 `{}`: {error}", path.display())))
83}
84
85#[cfg(not(unix))]
86pub(crate) fn set_restrictive_permissions(_path: &Path) -> Result<(), AuthError> {
87 Ok(())
88}
89
90#[cfg(feature = "http-axum")]
91pub(crate) fn duration_secs_i64(duration: Duration, field: &str) -> Result<i64, AuthError> {
92 i64::try_from(duration.as_secs())
93 .map_err(|_| AuthError::Config(format!("{field} exceeds supported range")))
94}
95
96#[cfg(feature = "http-axum")]
97pub(crate) fn duration_secs_usize(duration: Duration, field: &str) -> Result<usize, AuthError> {
98 usize::try_from(duration.as_secs())
99 .map_err(|_| AuthError::Config(format!("{field} exceeds supported range")))
100}
101
102#[cfg(feature = "http-axum")]
103pub(crate) fn timestamp_usize(timestamp: i64, field: &str) -> Result<usize, AuthError> {
104 usize::try_from(timestamp)
105 .map_err(|_| AuthError::Storage(format!("{field} is negative or exceeds usize range")))
106}
107
108#[cfg(feature = "http-axum")]
109pub(crate) fn expires_at(
110 created_at: i64,
111 duration: Duration,
112 field: &str,
113) -> Result<i64, AuthError> {
114 let ttl = duration_secs_i64(duration, field)?;
115 created_at
116 .checked_add(ttl)
117 .ok_or_else(|| AuthError::Config(format!("{field} exceeds supported range")))
118}