this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Day six part one with actual text

+69 -40
+35 -17
shared/challenges_markdown/six/part_one.md
··· 1 - ## Service Authentication (serviceAuth) 1 + Ever wondered how you can prove to another service that you're who you say you are? That you are the logged-in 2 + atmosphere account? Remember way back on day 1 when we had you grab the `multikey` from your DID document under 3 + `verificationMethod`? That's actually the public key to the private key on the PDS that is used to sign the commits to 4 + your repo. It can also be used to create a JWT to prove it's you by the signature using the 5 + [com.atproto.server.getServiceAuth](https://docs.bsky.app/docs/api/com-atproto-server-get-service-auth) endpoint. 2 6 3 - In the AT Protocol, services authenticate requests between each other using **serviceAuth** — a signed JWT token. When your PDS proxies a request to another service on your behalf, it creates a JWT signed with your account's signing key. 7 + When calling this endpoint there are 2 required parameters: 4 8 5 - For this challenge, you'll create a serviceAuth JWT yourself. Here's what you need: 9 + - `aud`: This is the `did:web` of the service you expect to use this token. An example is when migrating to create a new 10 + account on the PDS with your did it would be the PDS's did like `did:web:blacksky.app`. 11 + - `lxm`: The XRPC endpoint you are calling, like `com.atproto.server.createAccount` in the case of a migration. 6 12 7 - ### JWT Structure 13 + The JWT created looks a bit like this decoded. 8 14 9 - Your JWT must have these **header** fields: 10 - - `alg`: `ES256` (if your signing key is P-256) or `ES256K` (if secp256k1) 11 - - `typ`: `JWT` 15 + header: 12 16 13 - And these **payload** claims: 14 - - `iss`: Your DID (e.g. `did:plc:abc123...`) 15 - - `aud`: `{{service_did}}` 16 - - `lxm`: `{{lxm_part_one}}` 17 - - `exp`: A UNIX timestamp in the future (current time + 60 seconds works) 18 - - `iat`: Current UNIX timestamp 17 + ```json 18 + { 19 + "typ": "JWT", 20 + "alg": "ES256K" 21 + } 22 + ``` 19 23 20 - Sign it with your account's **repo signing key** — the same key listed in your DID document's verification method. 24 + Payload: 21 25 22 - The resulting JWT is three base64url-encoded segments joined by dots: `header.payload.signature` 26 + ```json 27 + { 28 + "iat": 1774113399, 29 + "iss": "did:plc:rnpkyqnmsw4ipey6eotbdnnf", 30 + "aud": "did:web:blacksky.app", 31 + "exp": 1774113459, 32 + "lxm": "com.atproto.server.createAccount", 33 + "jti": "793cd20048417bcd8d229f343a95363f" 34 + } 35 + ``` 36 + 37 + On the backend usually you decode the JWT, accessed the `iss` claim, resolve the did document, and from there can then 38 + verify the signature. 23 39 24 - ### Submit Your JWT 40 + In the AT Protocol, services authenticate requests between each other using **serviceAuth** — a signed JWT token. When 41 + your PDS proxies a request to another service on your behalf, it creates a JWT signed with your account's signing key. 25 42 26 - Paste the raw JWT string below and we'll verify the signature against your DID document. 43 + For this challenge request a serviceAuth token for your account for the lxm `{{lxm_part_one}}` endpoint with the `aud` 44 + of `{{service_did}}`.
+23 -13
shared/src/advent/challenges/day_six.rs
··· 2 2 use crate::OAuthAgentType; 3 3 use crate::advent::day::Day; 4 4 use crate::advent::{AdventChallenge, AdventError, AdventPart, ChallengeCheckResponse}; 5 - use crate::atrium::service_auth::{decode_and_verify_service_auth, decode_jwt_claims, extract_signing_key_bytes}; 5 + use crate::atrium::service_auth::{ 6 + decode_and_verify_service_auth, decode_jwt_claims, extract_signing_key_bytes, 7 + }; 6 8 use async_trait::async_trait; 7 9 use atrium_api::types::string::Did; 8 10 use atrium_common::resolver::Resolver; 9 11 use serde_json::json; 10 12 use sqlx::PgPool; 11 13 12 - pub const LXM_PART_ONE: &str = "codes.advent.challenge.verifyDay6"; 13 - pub const LXM_PART_TWO: &str = "codes.advent.challenge.getDay6Code"; 14 + pub const LXM_PART_ONE: &str = "codes.advent.challenge.exampleEndpoint"; 15 + pub const LXM_PART_TWO: &str = "codes.advent.challenge.getChallengeCode"; 14 16 15 17 pub struct DaySix { 16 18 pub pool: PgPool, ··· 48 50 _code: &str, 49 51 ) -> Result<Option<serde_json::Value>, AdventError> { 50 52 // Strip "did:web:" prefix for the domain used in URLs 51 - let domain = self.service_did.strip_prefix("did:web:").unwrap_or(&self.service_did); 53 + let domain = self 54 + .service_did 55 + .strip_prefix("did:web:") 56 + .unwrap_or(&self.service_did); 52 57 Ok(Some(json!({ 53 58 "service_did": self.service_did, 54 59 "service_did_domain": domain, ··· 117 122 let iss_did: Did = claims.iss.parse().map_err(|_| { 118 123 AdventError::ShouldNotHappen(format!("Invalid DID in JWT iss: {}", claims.iss)) 119 124 })?; 120 - let did_doc = self.handle_resolver.resolve(&iss_did).await.map_err(|err| { 121 - log::error!("Failed to resolve DID document for {}: {}", claims.iss, err); 122 - AdventError::ShouldNotHappen(format!("Failed to resolve DID document: {err}")) 123 - })?; 125 + let did_doc = self 126 + .handle_resolver 127 + .resolve(&iss_did) 128 + .await 129 + .map_err(|err| { 130 + log::error!("Failed to resolve DID document for {}: {}", claims.iss, err); 131 + AdventError::ShouldNotHappen(format!("Failed to resolve DID document: {err}")) 132 + })?; 124 133 125 134 let (key_alg, key_bytes) = match extract_signing_key_bytes(&did_doc) { 126 135 Ok(result) => result, ··· 163 172 )) 164 173 } 165 174 Some(challenge) => { 166 - let expected = challenge 167 - .verification_code_two 168 - .ok_or(AdventError::ShouldNotHappen( 169 - "No verification code for part two".to_string(), 170 - ))?; 175 + let expected = 176 + challenge 177 + .verification_code_two 178 + .ok_or(AdventError::ShouldNotHappen( 179 + "No verification code for part two".to_string(), 180 + ))?; 171 181 172 182 if submitted_code == expected { 173 183 Ok(ChallengeCheckResponse::Correct)
+11 -10
web/src/main.rs
··· 4 4 templates::error::ErrorTemplate, 5 5 templates::home::{DayStatus, HomeTemplate}, 6 6 }; 7 - use atrium_api::agent::atp_agent::store::MemorySessionStore; 8 7 use atrium_api::agent::atp_agent::AtpAgent; 8 + use atrium_api::agent::atp_agent::store::MemorySessionStore; 9 9 use atrium_identity::{ 10 10 did::{CommonDidResolver, CommonDidResolverConfig, DEFAULT_PLC_DIRECTORY_URL}, 11 11 handle::{AtprotoHandleResolver, AtprotoHandleResolverConfig}, ··· 37 37 atrium::stores::AtriumStateStore, 38 38 }; 39 39 use sqlx::{PgPool, migrate::Migrator, postgres::PgPoolOptions}; 40 - use std::{ 41 - env, 42 - net::SocketAddr, 43 - sync::Arc, 44 - time, 45 - }; 40 + use std::{env, net::SocketAddr, sync::Arc, time}; 46 41 use time::Duration; 47 42 use tower_http::trace::TraceLayer; 48 43 use tower_sessions::{SessionManagerLayer, cookie::SameSite}; ··· 266 261 let prod: bool = env::var("PROD") 267 262 .map(|val| val == "true") 268 263 .unwrap_or_else(|_| true); 269 - log::info!("listening on http://{addr} (mode: {})", if prod { "PROD" } else { "DEV" }); 264 + log::info!( 265 + "listening on http://{addr} (mode: {})", 266 + if prod { "PROD" } else { "DEV" } 267 + ); 270 268 271 269 let mut app = Router::new() 272 270 .route("/", get(home_handler)) ··· 293 291 get(handlers::day::day_five_create_record_handler), 294 292 ) 295 293 .route( 296 - "/xrpc/codes.advent.challenge.getDay6Code", 294 + "/xrpc/codes.advent.challenge.getChallengeCode", 297 295 get(handlers::day::day_six_xrpc_handler), 298 296 ) 299 - .route("/leaderboard", get(handlers::leaderboard::leaderboard_handler)) 297 + .route( 298 + "/leaderboard", 299 + get(handlers::leaderboard::leaderboard_handler), 300 + ) 300 301 .route("/login", get(handlers::auth::login_page_handler)) 301 302 .route("/logout", get(handlers::auth::logout_handler)) 302 303 .route("/redirect/login", get(handlers::auth::login_handle))