Auto-indexing service and GraphQL API for AT Protocol Records quickslice.slices.network/
atproto gleam graphql
at main 280 lines 7.9 kB view raw
1import atproto_auth 2import database/executor.{type Executor, Int, Text} 3import database/repositories/oauth_access_tokens 4import database/repositories/oauth_atp_sessions 5import database/types.{Bearer, OAuthAccessToken, OAuthAtpSession} 6import gleam/option.{None, Some} 7import gleeunit/should 8import lib/oauth/did_cache 9import test_helpers 10 11fn setup_db() -> Executor { 12 let assert Ok(exec) = test_helpers.create_test_db() 13 let assert Ok(_) = test_helpers.create_oauth_tables(exec) 14 // Create the test client that access tokens reference 15 let assert Ok(_) = 16 executor.exec( 17 exec, 18 "INSERT INTO oauth_client (client_id, client_name, redirect_uris, grant_types, response_types, token_endpoint_auth_method, client_type, created_at, updated_at) 19 VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", 20 [ 21 Text("test-client"), 22 Text("Test Client"), 23 Text("[\"http://localhost\"]"), 24 Text("[\"authorization_code\"]"), 25 Text("[\"code\"]"), 26 Text("none"), 27 Text("public"), 28 Int(1000), 29 Int(1000), 30 ], 31 ) 32 exec 33} 34 35pub fn verify_token_valid_returns_user_info_test() { 36 let exec = setup_db() 37 38 // Insert a valid token 39 let token = 40 OAuthAccessToken( 41 token: "valid-token", 42 token_type: Bearer, 43 client_id: "test-client", 44 user_id: Some("did:plc:testuser123"), 45 session_id: Some("session-123"), 46 session_iteration: Some(1), 47 scope: Some("atproto"), 48 created_at: 1000, 49 expires_at: 9_999_999_999, 50 revoked: False, 51 dpop_jkt: None, 52 ) 53 let assert Ok(_) = oauth_access_tokens.insert(exec, token) 54 55 let result = atproto_auth.verify_token(exec, "valid-token") 56 57 result |> should.be_ok 58 let assert Ok(user_info) = result 59 user_info.did |> should.equal("did:plc:testuser123") 60} 61 62pub fn verify_token_not_found_returns_error_test() { 63 let exec = setup_db() 64 65 let result = atproto_auth.verify_token(exec, "nonexistent-token") 66 67 result |> should.be_error 68 let assert Error(err) = result 69 err |> should.equal(atproto_auth.UnauthorizedToken) 70} 71 72pub fn verify_token_revoked_returns_error_test() { 73 let exec = setup_db() 74 75 let token = 76 OAuthAccessToken( 77 token: "revoked-token", 78 token_type: Bearer, 79 client_id: "test-client", 80 user_id: Some("did:plc:testuser123"), 81 session_id: Some("session-123"), 82 session_iteration: Some(1), 83 scope: Some("atproto"), 84 created_at: 1000, 85 expires_at: 9_999_999_999, 86 revoked: True, 87 dpop_jkt: None, 88 ) 89 let assert Ok(_) = oauth_access_tokens.insert(exec, token) 90 91 let result = atproto_auth.verify_token(exec, "revoked-token") 92 93 result |> should.be_error 94 let assert Error(err) = result 95 err |> should.equal(atproto_auth.UnauthorizedToken) 96} 97 98pub fn verify_token_expired_returns_error_test() { 99 let exec = setup_db() 100 101 let token = 102 OAuthAccessToken( 103 token: "expired-token", 104 token_type: Bearer, 105 client_id: "test-client", 106 user_id: Some("did:plc:testuser123"), 107 session_id: Some("session-123"), 108 session_iteration: Some(1), 109 scope: Some("atproto"), 110 created_at: 1000, 111 expires_at: 1, 112 // Already expired 113 revoked: False, 114 dpop_jkt: None, 115 ) 116 let assert Ok(_) = oauth_access_tokens.insert(exec, token) 117 118 let result = atproto_auth.verify_token(exec, "expired-token") 119 120 result |> should.be_error 121 let assert Error(err) = result 122 err |> should.equal(atproto_auth.TokenExpired) 123} 124 125pub fn verify_token_no_user_id_returns_error_test() { 126 let exec = setup_db() 127 128 let token = 129 OAuthAccessToken( 130 token: "no-user-token", 131 token_type: Bearer, 132 client_id: "test-client", 133 user_id: None, 134 session_id: Some("session-123"), 135 session_iteration: Some(1), 136 scope: Some("atproto"), 137 created_at: 1000, 138 expires_at: 9_999_999_999, 139 revoked: False, 140 dpop_jkt: None, 141 ) 142 let assert Ok(_) = oauth_access_tokens.insert(exec, token) 143 144 let result = atproto_auth.verify_token(exec, "no-user-token") 145 146 result |> should.be_error 147 let assert Error(err) = result 148 err |> should.equal(atproto_auth.UnauthorizedToken) 149} 150 151// ===== get_atp_session tests ===== 152 153pub fn get_atp_session_no_session_id_returns_error_test() { 154 let exec = setup_db() 155 156 // Token without session_id 157 let token = 158 OAuthAccessToken( 159 token: "no-session-token", 160 token_type: Bearer, 161 client_id: "test-client", 162 user_id: Some("did:plc:testuser123"), 163 session_id: None, 164 session_iteration: None, 165 scope: Some("atproto"), 166 created_at: 1000, 167 expires_at: 9_999_999_999, 168 revoked: False, 169 dpop_jkt: None, 170 ) 171 let assert Ok(_) = oauth_access_tokens.insert(exec, token) 172 173 let assert Ok(cache) = did_cache.start() 174 175 let result = 176 atproto_auth.get_atp_session(exec, cache, "no-session-token", None, "") 177 178 result |> should.be_error 179 let assert Error(err) = result 180 err |> should.equal(atproto_auth.SessionNotFound) 181} 182 183pub fn get_atp_session_not_exchanged_returns_error_test() { 184 let exec = setup_db() 185 186 // Token with session_id 187 let token = 188 OAuthAccessToken( 189 token: "has-session-token", 190 token_type: Bearer, 191 client_id: "test-client", 192 user_id: Some("did:plc:testuser123"), 193 session_id: Some("session-123"), 194 session_iteration: Some(1), 195 scope: Some("atproto"), 196 created_at: 1000, 197 expires_at: 9_999_999_999, 198 revoked: False, 199 dpop_jkt: None, 200 ) 201 let assert Ok(_) = oauth_access_tokens.insert(exec, token) 202 203 // ATP session that hasn't been exchanged yet (no access_token, no session_exchanged_at) 204 let atp_session = 205 OAuthAtpSession( 206 session_id: "session-123", 207 iteration: 1, 208 did: Some("did:plc:testuser123"), 209 session_created_at: 1000, 210 atp_oauth_state: "state-123", 211 signing_key_jkt: "jkt-123", 212 dpop_key: "{\"kty\":\"EC\"}", 213 access_token: None, 214 refresh_token: None, 215 access_token_created_at: None, 216 access_token_expires_at: None, 217 access_token_scopes: None, 218 session_exchanged_at: None, 219 exchange_error: None, 220 ) 221 let assert Ok(_) = oauth_atp_sessions.insert(exec, atp_session) 222 223 let assert Ok(cache) = did_cache.start() 224 225 let result = 226 atproto_auth.get_atp_session(exec, cache, "has-session-token", None, "") 227 228 result |> should.be_error 229 let assert Error(err) = result 230 err |> should.equal(atproto_auth.SessionNotReady) 231} 232 233pub fn get_atp_session_with_exchange_error_returns_error_test() { 234 let exec = setup_db() 235 236 let token = 237 OAuthAccessToken( 238 token: "error-session-token", 239 token_type: Bearer, 240 client_id: "test-client", 241 user_id: Some("did:plc:testuser123"), 242 session_id: Some("session-456"), 243 session_iteration: Some(1), 244 scope: Some("atproto"), 245 created_at: 1000, 246 expires_at: 9_999_999_999, 247 revoked: False, 248 dpop_jkt: None, 249 ) 250 let assert Ok(_) = oauth_access_tokens.insert(exec, token) 251 252 // ATP session with exchange error 253 let atp_session = 254 OAuthAtpSession( 255 session_id: "session-456", 256 iteration: 1, 257 did: Some("did:plc:testuser123"), 258 session_created_at: 1000, 259 atp_oauth_state: "state-456", 260 signing_key_jkt: "jkt-456", 261 dpop_key: "{\"kty\":\"EC\"}", 262 access_token: Some("token"), 263 refresh_token: Some("refresh"), 264 access_token_created_at: Some(1000), 265 access_token_expires_at: Some(9_999_999_999), 266 access_token_scopes: Some("atproto"), 267 session_exchanged_at: Some(1000), 268 exchange_error: Some("exchange failed"), 269 ) 270 let assert Ok(_) = oauth_atp_sessions.insert(exec, atp_session) 271 272 let assert Ok(cache) = did_cache.start() 273 274 let result = 275 atproto_auth.get_atp_session(exec, cache, "error-session-token", None, "") 276 277 result |> should.be_error 278 let assert Error(err) = result 279 err |> should.equal(atproto_auth.SessionNotReady) 280}