@@ -2865,14 +2865,16 @@ async function handleCompleteAuthorisationSession(
28652865 request_id : requestId ,
28662866 session_id : session . session_id ,
28672867 status : session . status ,
2868+ ...( session . namespace_status ? { namespace_status : session . namespace_status } : { } ) ,
28682869 } , 200 , headers ) ;
28692870 }
28702871 if ( ! session . payload || ! session . challenge_token_hash ) {
28712872 throw new ApiError ( 409 , "authorisation_challenge_missing" , "request a wallet challenge before completing this session" ) ;
28722873 }
28732874 const body = await readJson ( request , Math . min ( maxJsonBytes ( env ) , 128 * 1024 ) ) ;
28742875 const challengeToken = String ( body [ "challenge_token" ] ?? "" ) . trim ( ) ;
2875- if ( ! challengeToken || `sha256:${ await sha256Hex ( challengeToken ) } ` !== session . challenge_token_hash ) {
2876+ const challengeTokenHash = `sha256:${ await sha256Hex ( challengeToken ) } ` ;
2877+ if ( ! challengeToken || ! await constantTimeSecretEqual ( challengeTokenHash , session . challenge_token_hash ) ) {
28762878 throw new ApiError ( 401 , "invalid_authorisation_challenge_token" , "authorisation challenge token is invalid or stale" ) ;
28772879 }
28782880 const payload = validateCapabilityPayload ( session . payload , registryOrigin , now ) ;
@@ -2884,66 +2886,38 @@ async function handleCompleteAuthorisationSession(
28842886 if ( existing && ( existing . owner_principal_type !== payload . principal_type || existing . owner_principal_id !== payload . principal_id ) ) {
28852887 throw new ApiError ( 409 , "namespace_already_claimed" , "namespace is already claimed by another principal" ) ;
28862888 }
2887- if ( ! existing ) {
2888- await enforceNamespaceClaimCooldown (
2889- store ,
2890- requestId ,
2891- payload . principal_type ,
2892- payload . principal_id ,
2893- now ,
2894- namespaceClaimCooldownSeconds ( env ) ,
2895- ) ;
2896- }
2897- const nonceKey = await consumeSignedNonce ( store , requestId , {
2889+ const nonce = await signedNonceUse ( requestId , {
28982890 protocol : payload . protocol ,
28992891 action : `${ payload . action } :capability_create` ,
29002892 nonce : payload . nonce ,
29012893 expires_at : payload . expires_at ,
29022894 principal_type : payload . principal_type ,
29032895 principal_id : payload . principal_id ,
29042896 } ) ;
2905- let capability ;
2906- try {
2907- capability = await store . recordCapability ( { payload, principal_signature : signature , request_id : requestId } ) ;
2908- } catch ( error ) {
2909- await store . releaseNonce ( { nonce_key : nonceKey , request_id : requestId } ) ;
2910- throw error ;
2911- }
2912- let namespaceStatus : "active" | "review_pending" ;
2913- if ( existing ) {
2914- namespaceStatus = existing . status === "active" ? "active" : "review_pending" ;
2915- } else {
2916- const claim = await store . claimNamespace ( {
2917- namespace : session . namespace ,
2897+ const completion = await store . finaliseAuthorisationSession ( {
2898+ session_id : sessionId ,
2899+ expected_challenge_token_hash : challengeTokenHash ,
2900+ payload,
2901+ principal_signature : signature ,
2902+ nonce : {
2903+ ...nonce ,
29182904 principal_type : payload . principal_type ,
29192905 principal_id : payload . principal_id ,
2920- request_id : requestId ,
2921- } ) ;
2922- namespaceStatus = claim . status ;
2923- }
2924- const completed = await store . completeAuthorisationSession ( {
2925- session_id : sessionId ,
2926- capability_key_id : capability . key_id ,
2927- namespace_status : namespaceStatus ,
2928- request_id : requestId ,
2929- } ) ;
2930- await store . appendAuditEvent ( {
2906+ } ,
29312907 request_id : requestId ,
2932- event_type : "authorisation_session.completed" ,
2933- principal_type : payload . principal_type ,
2934- principal_id : payload . principal_id ,
2935- capability_key_id : capability . key_id ,
2936- namespace : session . namespace ,
2937- name : session . name ,
2938- data : { session_id : session . session_id , namespace_status : namespaceStatus } ,
2908+ now_iso : now . toISOString ( ) ,
2909+ namespace_claim_cooldown_seconds : namespaceClaimCooldownSeconds ( env ) ,
29392910 } ) ;
2911+ const completed = completion . session ;
2912+ const namespaceStatus = completed . namespace_status ;
2913+ if ( ! namespaceStatus ) throw new Error ( "completed authorisation session did not record namespace status" ) ;
29402914 return json ( {
29412915 schema : "cellscript-registry-authorisation-session-v1" ,
29422916 request_id : requestId ,
29432917 session_id : completed . session_id ,
29442918 status : completed . status ,
29452919 namespace_status : namespaceStatus ,
2946- } , namespaceStatus === "active" ? 201 : 202 , headers ) ;
2920+ } , completion . replayed ? 200 : namespaceStatus === "active" ? 201 : 202 , headers ) ;
29472921}
29482922
29492923function validateAuthorisationSessionId ( value : string ) : string {
@@ -3490,19 +3464,17 @@ function idempotencyResponse(record: IdempotencyRecord, headers: Headers): Respo
34903464 return json ( record . response_body , record . response_status , replayHeaders ) ;
34913465}
34923466
3493- async function consumeSignedNonce (
3494- store : RegistryStore ,
3495- requestId : string ,
3496- input : {
3497- protocol : string ;
3498- action : string ;
3499- nonce : string ;
3500- expires_at : string ;
3501- principal_type ?: string ;
3502- principal_id ?: string ;
3503- capability_key_id ?: string ;
3504- } ,
3505- ) : Promise < string > {
3467+ type SignedNonceUseSource = {
3468+ protocol : string ;
3469+ action : string ;
3470+ nonce : string ;
3471+ expires_at : string ;
3472+ principal_type ?: PrincipalType ;
3473+ principal_id ?: string ;
3474+ capability_key_id ?: string ;
3475+ } ;
3476+
3477+ async function signedNonceUse ( requestId : string , input : SignedNonceUseSource ) {
35063478 const nonceKey = `nonce_${ await sha256Hex ( canonicalJson ( {
35073479 protocol : input . protocol ,
35083480 action : input . action ,
@@ -3511,7 +3483,7 @@ async function consumeSignedNonce(
35113483 principal_id : input . principal_id ?? null ,
35123484 capability_key_id : input . capability_key_id ?? null ,
35133485 } ) ) } `;
3514- const accepted = await store . consumeNonce ( {
3486+ return {
35153487 nonce_key : nonceKey ,
35163488 protocol : input . protocol ,
35173489 action : input . action ,
@@ -3521,7 +3493,16 @@ async function consumeSignedNonce(
35213493 ...( input . principal_type ? { principal_type : input . principal_type } : { } ) ,
35223494 ...( input . principal_id ? { principal_id : input . principal_id } : { } ) ,
35233495 ...( input . capability_key_id ? { capability_key_id : input . capability_key_id } : { } ) ,
3524- } ) ;
3496+ } ;
3497+ }
3498+
3499+ async function consumeSignedNonce (
3500+ store : RegistryStore ,
3501+ requestId : string ,
3502+ input : SignedNonceUseSource ,
3503+ ) : Promise < string > {
3504+ const nonceUse = await signedNonceUse ( requestId , input ) ;
3505+ const accepted = await store . consumeNonce ( nonceUse ) ;
35253506 if ( ! accepted ) {
35263507 await store . appendAuditEvent ( {
35273508 request_id : requestId ,
@@ -3532,12 +3513,12 @@ async function consumeSignedNonce(
35323513 data : {
35333514 protocol : input . protocol ,
35343515 action : input . action ,
3535- nonce_key : nonceKey ,
3516+ nonce_key : nonceUse . nonce_key ,
35363517 } ,
35373518 } ) ;
35383519 throw new ApiError ( 409 , "nonce_replay" , "signed nonce has already been used" ) ;
35393520 }
3540- return nonceKey ;
3521+ return nonceUse . nonce_key ;
35413522}
35423523
35433524async function writeStaticRegistryVersionObject (
0 commit comments