forked from
rocksky.app/rocksky
A decentralized music tracking and discovery platform built on AT Protocol 馃幍
1use anyhow::Error;
2use jsonwebtoken::DecodingKey;
3use jsonwebtoken::EncodingKey;
4use jsonwebtoken::Header;
5use jsonwebtoken::Validation;
6use serde::{Deserialize, Serialize};
7use sqlx::{Pool, Postgres};
8use std::collections::BTreeMap;
9use std::env;
10
11use crate::cache::Cache;
12use crate::repo;
13use crate::signature::generate_signature;
14use crate::xata::user::User;
15
16#[derive(Debug, Serialize, Deserialize)]
17pub struct Claims {
18 pub exp: usize,
19 pub iat: usize,
20 pub did: String,
21}
22
23pub async fn authenticate_v1(
24 pool: &Pool<Postgres>,
25 api_key: &str,
26 timestamp: &str,
27 password_md5: &str,
28) -> Result<(), Error> {
29 match repo::user::get_user_by_apikey(pool, api_key).await? {
30 Some(user) => {
31 let shared_secret = user
32 .shared_secret
33 .ok_or_else(|| Error::msg("User does not have a shared secret"))?;
34 let hashed_password = md5::compute(format!("{}", shared_secret));
35 let hashed_password = format!("{:x}", hashed_password);
36 let expected_password = format!("{}{}", hashed_password, timestamp);
37 let expected_password = md5::compute(expected_password);
38 let expected_password = format!("{:x}", expected_password);
39 if expected_password != password_md5 {
40 println!("{} != {}", expected_password, password_md5);
41 return Err(Error::msg("Invalid password"));
42 }
43 Ok(())
44 }
45 None => Err(Error::msg("Invalid API key")),
46 }
47}
48
49pub async fn authenticate(
50 pool: &Pool<Postgres>,
51 api_key: &str,
52 api_sig: &str,
53 session_key: &str,
54 form: &BTreeMap<String, String>,
55) -> Result<(), Error> {
56 let claims = decode_token(session_key)?;
57
58 let user_apikey = repo::api_key::get_apikey(pool, api_key, &claims.did).await?;
59
60 if user_apikey.is_none() {
61 return Err(Error::msg("Invalid API key"));
62 }
63
64 let user_apikey = user_apikey.unwrap();
65
66 let signature = generate_signature(form, &user_apikey.shared_secret);
67
68 if signature != api_sig {
69 return Err(Error::msg("Invalid signature"));
70 }
71
72 Ok(())
73}
74
75pub async fn extract_did(
76 pool: &Pool<Postgres>,
77 form: &BTreeMap<String, String>,
78) -> Result<String, Error> {
79 let apikey = form
80 .get("api_key")
81 .ok_or_else(|| Error::msg("Missing api_key"))?;
82 let user = repo::user::get_user_by_apikey(pool, apikey).await?;
83 let did = user
84 .ok_or_else(|| Error::msg("Corresponding user not found"))?
85 .did;
86 Ok(did)
87}
88
89pub fn generate_token(did: &str) -> Result<String, Error> {
90 if env::var("JWT_SECRET").is_err() {
91 return Err(Error::msg("JWT_SECRET is not set"));
92 }
93
94 let claims = Claims {
95 exp: chrono::Utc::now().timestamp() as usize + 3600,
96 iat: chrono::Utc::now().timestamp() as usize,
97 did: did.to_string(),
98 };
99
100 jsonwebtoken::encode(
101 &Header::default(),
102 &claims,
103 &EncodingKey::from_secret(env::var("JWT_SECRET")?.as_ref()),
104 )
105 .map_err(Into::into)
106}
107
108pub fn decode_token(token: &str) -> Result<Claims, Error> {
109 if env::var("JWT_SECRET").is_err() {
110 return Err(Error::msg("JWT_SECRET is not set"));
111 }
112
113 jsonwebtoken::decode::<Claims>(
114 token,
115 &DecodingKey::from_secret(env::var("JWT_SECRET")?.as_ref()),
116 &Validation::default(),
117 )
118 .map(|data| data.claims)
119 .map_err(Into::into)
120}
121
122pub async fn generate_session_id(
123 pool: &Pool<Postgres>,
124 cache: &Cache,
125 api_key: &str,
126) -> Result<String, Error> {
127 match repo::user::get_user_by_apikey(pool, &api_key).await? {
128 Some(user) => {
129 let mut bytes = [0u8; 16];
130 rand::fill(&mut bytes[..]);
131
132 let session_id = hex::encode(bytes);
133
134 let user =
135 serde_json::to_string(&user).map_err(|_| Error::msg("Failed to serialize user"))?;
136 cache.set(&format!("lastfm:{}", session_id), &user)?;
137 Ok(session_id)
138 }
139 None => Err(Error::msg("Invalid API key")),
140 }
141}
142
143pub fn verify_session_id(cache: &Cache, session_id: &str) -> Result<String, Error> {
144 let user = cache.get(&format!("lastfm:{}", session_id))?;
145 if user.is_none() {
146 return Err(Error::msg("Session ID not found"));
147 }
148 let user: String = user.unwrap();
149 let user: User = serde_json::from_str(&user)
150 .map_err(|e| Error::msg(format!("Failed to deserialize user: {}", e)))?;
151 Ok(user.xata_id)
152}
153
154#[cfg(test)]
155mod tests {
156 use dotenv::dotenv;
157
158 use super::*;
159
160 #[test]
161 fn test_generate_token() {
162 dotenv().ok();
163 let token = generate_token("did:plc:7vdlgi2bflelz7mmuxoqjfcr").unwrap();
164 let claims = decode_token(&token).unwrap();
165
166 assert_eq!(claims.did, "did:plc:7vdlgi2bflelz7mmuxoqjfcr");
167 }
168}