Alternative ATProto PDS implementation
1//! Based on https://github.com/blacksky-algorithms/rsky/blob/main/rsky-pds/src/account_manager/helpers/email_token.rs
2//! blacksky-algorithms/rsky is licensed under the Apache License 2.0
3//!
4//! Modified for SQLite backend
5use crate::models::pds::EmailToken;
6use crate::models::pds::EmailTokenPurpose;
7use anyhow::{Result, bail};
8use diesel::*;
9use rsky_common::time::{MINUTE, from_str_to_utc, less_than_ago_s};
10use rsky_pds::apis::com::atproto::server::get_random_token;
11
12pub async fn create_email_token(
13 did: &str,
14 purpose: EmailTokenPurpose,
15 db: &deadpool_diesel::Pool<
16 deadpool_diesel::Manager<SqliteConnection>,
17 deadpool_diesel::sqlite::Object,
18 >,
19) -> Result<String> {
20 use crate::schema::pds::email_token::dsl as EmailTokenSchema;
21 let token = get_random_token().to_uppercase();
22 let now = rsky_common::now();
23
24 let did = did.to_owned();
25 db.get()
26 .await?
27 .interact(move |conn| {
28 _ = insert_into(EmailTokenSchema::email_token)
29 .values((
30 EmailTokenSchema::purpose.eq(purpose),
31 EmailTokenSchema::did.eq(did),
32 EmailTokenSchema::token.eq(&token),
33 EmailTokenSchema::requestedAt.eq(&now),
34 ))
35 .on_conflict((EmailTokenSchema::purpose, EmailTokenSchema::did))
36 .do_update()
37 .set((
38 EmailTokenSchema::token.eq(&token),
39 EmailTokenSchema::requestedAt.eq(&now),
40 ))
41 .execute(conn)?;
42 Ok(token)
43 })
44 .await
45 .expect("Failed to create email token")
46}
47
48pub async fn assert_valid_token(
49 did: &str,
50 purpose: EmailTokenPurpose,
51 token: &str,
52 expiration_len: Option<i32>,
53 db: &deadpool_diesel::Pool<
54 deadpool_diesel::Manager<SqliteConnection>,
55 deadpool_diesel::sqlite::Object,
56 >,
57) -> Result<()> {
58 let expiration_len = expiration_len.unwrap_or(MINUTE * 15);
59 use crate::schema::pds::email_token::dsl as EmailTokenSchema;
60
61 let did = did.to_owned();
62 let token = token.to_owned();
63 let res = db
64 .get()
65 .await?
66 .interact(move |conn| {
67 EmailTokenSchema::email_token
68 .filter(EmailTokenSchema::purpose.eq(purpose))
69 .filter(EmailTokenSchema::did.eq(did))
70 .filter(EmailTokenSchema::token.eq(token.to_uppercase()))
71 .select(EmailToken::as_select())
72 .first(conn)
73 .optional()
74 })
75 .await
76 .expect("Failed to assert token")?;
77 if let Some(res) = res {
78 let requested_at = from_str_to_utc(&res.requested_at);
79 let expired = !less_than_ago_s(requested_at, expiration_len);
80 if expired {
81 bail!("Token is expired")
82 }
83 Ok(())
84 } else {
85 bail!("Token is invalid")
86 }
87}
88
89pub async fn assert_valid_token_and_find_did(
90 purpose: EmailTokenPurpose,
91 token: &str,
92 expiration_len: Option<i32>,
93 db: &deadpool_diesel::Pool<
94 deadpool_diesel::Manager<SqliteConnection>,
95 deadpool_diesel::sqlite::Object,
96 >,
97) -> Result<String> {
98 let expiration_len = expiration_len.unwrap_or(MINUTE * 15);
99 use crate::schema::pds::email_token::dsl as EmailTokenSchema;
100
101 let token = token.to_owned();
102 let res = db
103 .get()
104 .await?
105 .interact(move |conn| {
106 EmailTokenSchema::email_token
107 .filter(EmailTokenSchema::purpose.eq(purpose))
108 .filter(EmailTokenSchema::token.eq(token.to_uppercase()))
109 .select(EmailToken::as_select())
110 .first(conn)
111 .optional()
112 })
113 .await
114 .expect("Failed to assert token")?;
115 if let Some(res) = res {
116 let requested_at = from_str_to_utc(&res.requested_at);
117 let expired = !less_than_ago_s(requested_at, expiration_len);
118 if expired {
119 bail!("Token is expired")
120 }
121 Ok(res.did)
122 } else {
123 bail!("Token is invalid")
124 }
125}
126
127pub async fn delete_email_token(
128 did: &str,
129 purpose: EmailTokenPurpose,
130 db: &deadpool_diesel::Pool<
131 deadpool_diesel::Manager<SqliteConnection>,
132 deadpool_diesel::sqlite::Object,
133 >,
134) -> Result<()> {
135 use crate::schema::pds::email_token::dsl as EmailTokenSchema;
136 let did = did.to_owned();
137 _ = db
138 .get()
139 .await?
140 .interact(move |conn| {
141 delete(EmailTokenSchema::email_token)
142 .filter(EmailTokenSchema::did.eq(did))
143 .filter(EmailTokenSchema::purpose.eq(purpose))
144 .execute(conn)
145 })
146 .await
147 .expect("Failed to delete token")?;
148 Ok(())
149}
150
151pub async fn delete_all_email_tokens(
152 did: &str,
153 db: &deadpool_diesel::Pool<
154 deadpool_diesel::Manager<SqliteConnection>,
155 deadpool_diesel::sqlite::Object,
156 >,
157) -> Result<()> {
158 use crate::schema::pds::email_token::dsl as EmailTokenSchema;
159
160 let did = did.to_owned();
161 _ = db
162 .get()
163 .await?
164 .interact(move |conn| {
165 delete(EmailTokenSchema::email_token)
166 .filter(EmailTokenSchema::did.eq(did))
167 .execute(conn)
168 })
169 .await
170 .expect("Failed to delete all tokens")?;
171
172 Ok(())
173}