1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
use crate::{
errors::Error,
mail::{Challenge, ChallengeData, Notification},
secret::{PasswordHash, Secret},
sessions::SessionData,
users::{UserData, UserID},
NIST_MINIMUM_PASSWORD_LENGTH,
};
pub trait App: AppConfig + AppDb + AppMailer + AppTypes + 'static {
/// Returns the current time.
fn time_now(&self) -> Self::DateTime;
}
pub trait AppTypes: Sized {
/// The type of a numeric ID in the database; usually `i64`, `i32`, etc.
type ID: Into<i64> + TryFrom<i64> + Eq + Copy + std::fmt::Display;
/// The type used to represent a date and time in the application.
type DateTime: Copy + Ord + core::ops::Add<std::time::Duration, Output = Self::DateTime>;
/// The type of a user in the application.
type User: UserID<Self::ID> + Clone;
/// A type representing custom email challenges which can be issued by the
/// application. Use `authlib::mail::NoCustomChallenges` if there are none.
type CustomChallenge: serde::Serialize + for<'de> serde::Deserialize<'de>;
/// A type representing an application error. This must support conversion
/// from `authlib::Error`.
type Error: From<Error> + actix_web::ResponseError;
}
/// This trait defines functions which provide configuration parameters to the
/// authentication library.
#[allow(unused)]
pub trait AppConfig {
/// Returns the minimum password length.
///
/// Default is 8, as recommended by NIST.
fn minimum_password_length(&self) -> usize {
NIST_MINIMUM_PASSWORD_LENGTH
}
/// Returns the number of hours after which a challenge expires, if it is
/// not completed.
///
/// Default is 1 day.
fn challenge_expire_after_hours(&self) -> u64 {
1 * 24
}
/// Returns the number of hours after which a session expires, if it is not
/// renewed.
///
/// Default is 90 days.
fn session_expire_after_hours(&self) -> u64 {
90 * 24
}
/// Returns the number of hours after which a session should be renewed, if
/// it has not expired yet.
///
/// Default is 2 days.
fn session_renew_after_hours(&self) -> u64 {
2 * 24
}
/// Returns the name of the cookie which holds the session token.
///
/// Default is `"session"``.
fn session_token_cookie_name(&self) -> &str {
"session"
}
/// Indicates whether a `Same-Site: strict` header should be sent with the
/// session cookie. If `false`, a `Same-Site: lax` header will be sent
/// instead.
///
/// Default is `false`.
fn session_token_cookie_same_site_strict(&self) -> bool {
false
}
}
/// This trait defines functions which will be used by the authentication
/// library to store and retrieve data about users, sessions and challenges.
#[trait_variant::make(Send)]
pub trait AppDb: AppTypes {
type DbError: Into<Self::Error>;
/// Gets a user's data, including their password hash and active state, by
/// their id.
///
/// Returns `None` if there is no user with that identifier.
async fn get_user_data_by_id(
&mut self,
user_id: Self::ID,
) -> Result<Option<UserData<Self>>, Self::DbError>;
/// Indicates whether a given user identifier (e.g. username or email)
/// belongs to an existing user.
async fn user_identifier_exists(
&mut self,
user_identifier: &str,
) -> Result<bool, Self::DbError>;
/// Gets a user's data, including their password hash and active state, by
/// their identifier (e.g. username or email).
///
/// Returns `None` if there is no user with that identifier.
async fn get_user_data_by_identifier(
&mut self,
user_identifier: &str,
) -> Result<Option<UserData<Self>>, Self::DbError>;
/// Inserts a new user, returning the new user's unique id.
async fn insert_user(
&mut self,
user_data: UserData<Self>,
) -> Result<Self::ID, Self::DbError>;
/// Updates an unverified user to mark them as verified.
async fn verify_user(&mut self, user: &Self::User) -> Result<(), Self::DbError>;
/// Updates a user's stored password hash, also recording whether they are
/// subsequently required to change their password.
async fn update_password(
&mut self,
user: &Self::User,
password_hash: PasswordHash,
then_require_change: bool,
) -> Result<(), Self::DbError>;
/// Deletes a user by their id, if they exist. It is not an error to
/// attempt to delete a non-existent user.
async fn delete_user(&mut self, user_id: Self::ID) -> Result<(), Self::DbError>;
/// Gets the data for a session, including the user, whether the user is
/// active, the session token hash, and the session expiry time.
///
/// Returns `None` if there is no session with that id.
async fn get_session_by_id(
&mut self,
session_id: Self::ID,
) -> Result<Option<SessionData<Self>>, Self::DbError>;
/// Inserts a new session, returning the new session's unique id.
async fn insert_session(
&mut self,
user: &Self::User,
token_hash: Secret,
expires: Self::DateTime,
) -> Result<Self::ID, Self::DbError>;
/// Updates a session's stored token hash and expiry time.
async fn update_session_by_id(
&mut self,
session_id: Self::ID,
new_token_hash: Secret,
expires: Self::DateTime,
) -> Result<(), Self::DbError>;
/// Deletes a session by its id, if it exists. It is not an error to
/// attempt to delete a non-existent session.
async fn delete_session_by_id(&mut self, session_id: Self::ID) -> Result<(), Self::DbError>;
/// Gets the data for a challenge, including the user, the challenge type,
/// the challenge code hash, and the challenge expiry time.
///
/// Returns `None` if there is no challenge with that id.
async fn get_challenge_by_id(
&mut self,
challenge_id: Self::ID,
) -> Result<Option<ChallengeData<Self>>, Self::DbError>;
/// Inserts a new challenge, returning the new challenge's id.
async fn insert_challenge(
&mut self,
user: &Self::User,
challenge: &str,
code_hash: Secret,
expires: Self::DateTime,
) -> Result<Self::ID, Self::DbError>;
/// Deletes a challenge by its id, if it exists. It is not an error to
/// attempt to delete a non-existent challenge.
async fn delete_challenge_by_id(&mut self, challenge_id: Self::ID) -> Result<(), Self::DbError>;
}
/// This trait defines functions which will be used by the authentication
/// library to send email notifications and challenges to users.
#[trait_variant::make(Send)]
pub trait AppMailer: AppTypes {
type MailError: Into<Self::Error>;
/// Sends an email notification to the given user.
async fn send_notification(
&mut self,
user: &Self::User,
notification: Notification,
) -> Result<(), Self::MailError>;
/// Sends an email message to the given user, with a link to complete a
/// challenge. The link must match a route which invokes `complete_challenge(code)`.
async fn send_challenge(
&mut self,
user: &Self::User,
challenge: Challenge<Self>,
code: Secret,
) -> Result<(), Self::MailError>;
}