| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | import { | 1 | import { |
| 2 | RelayAgentActivityPublishProofPayload, | 2 | RelayAgentActivityPublishProofPayload, |
| | 3 | RelayAgentActivityPublishProofInvalidReason, |
| 3 | type RelayAgentActivityPublishRequest, | 4 | type RelayAgentActivityPublishRequest, |
| 4 | } from "@t3tools/contracts/relay"; | 5 | } from "@t3tools/contracts/relay"; |
| 5 | import { | 6 | import { |
| ... | @@ -23,11 +24,13 @@ import * as RelayConfiguration from "../Config.ts"; | ... | @@ -23,11 +24,13 @@ import * as RelayConfiguration from "../Config.ts"; |
| 23 | export class EnvironmentPublishSignatureExpired extends Schema.TaggedErrorClass<EnvironmentPublishSignatureExpired>()( | 24 | export class EnvironmentPublishSignatureExpired extends Schema.TaggedErrorClass<EnvironmentPublishSignatureExpired>()( |
| 24 | "EnvironmentPublishSignatureExpired", | 25 | "EnvironmentPublishSignatureExpired", |
| 25 | { | 26 | { |
| | 27 | environmentId: Schema.String, |
| | 28 | threadId: Schema.String, |
| 26 | expiresAt: Schema.String, | 29 | expiresAt: Schema.String, |
| 27 | }, | 30 | }, |
| 28 | ) { | 31 | ) { |
| 29 | override get message(): string { | 32 | override get message(): string { |
| 30 | return `Environment publish signature expired at ${this.expiresAt}`; | 33 | return `Environment '${this.environmentId}' publish signature for thread '${this.threadId}' expired at ${this.expiresAt}`; |
| 31 | } | 34 | } |
| 32 | } | 35 | } |
| 33 | | 36 | |
| ... | @@ -35,10 +38,21 @@ export class EnvironmentPublishSignatureInvalid extends Schema.TaggedErrorClass< | ... | @@ -35,10 +38,21 @@ export class EnvironmentPublishSignatureInvalid extends Schema.TaggedErrorClass< |
| 35 | "EnvironmentPublishSignatureInvalid", | 38 | "EnvironmentPublishSignatureInvalid", |
| 36 | { | 39 | { |
| 37 | environmentId: Schema.String, | 40 | environmentId: Schema.String, |
| | 41 | threadId: Schema.String, |
| | 42 | reason: RelayAgentActivityPublishProofInvalidReason, |
| | 43 | stage: Schema.Literals([ |
| | 44 | "decode_token", |
| | 45 | "verify_proof", |
| | 46 | "validate_claims", |
| | 47 | "validate_expiration", |
| | 48 | "generate_replay_thumbprint", |
| | 49 | "consume_nonce", |
| | 50 | ]), |
| | 51 | cause: Schema.optional(Schema.Defect()), |
| 38 | }, | 52 | }, |
| 39 | ) { | 53 | ) { |
| 40 | override get message(): string { | 54 | override get message(): string { |
| 41 | return `Environment '${this.environmentId}' publish signature is invalid`; | 55 | return `Environment '${this.environmentId}' publish signature for thread '${this.threadId}' is invalid during ${this.stage}: ${this.reason}`; |
| 42 | } | 56 | } |
| 43 | } | 57 | } |
| 44 | | 58 | |
| ... | @@ -102,13 +116,22 @@ const make = Effect.gen(function* () { | ... | @@ -102,13 +116,22 @@ const make = Effect.gen(function* () { |
| 102 | const now = yield* DateTime.now; | 116 | const now = yield* DateTime.now; |
| 103 | const decoded = yield* Effect.try({ | 117 | const decoded = yield* Effect.try({ |
| 104 | try: () => decodeRelayJwt(input.request.proof), | 118 | try: () => decodeRelayJwt(input.request.proof), |
| 105 | catch: () => new EnvironmentPublishSignatureInvalid({ environmentId: input.environmentId }), | 119 | catch: (cause) => |
| | 120 | new EnvironmentPublishSignatureInvalid({ |
| | 121 | environmentId: input.environmentId, |
| | 122 | threadId: input.threadId, |
| | 123 | reason: "invalid_signature_or_payload", |
| | 124 | stage: "decode_token", |
| | 125 | cause, |
| | 126 | }), |
| 106 | }); | 127 | }); |
| 107 | if ( | 128 | if ( |
| 108 | typeof decoded.exp === "number" && | 129 | typeof decoded.exp === "number" && |
| 109 | decoded.exp <= Math.floor(now.epochMilliseconds / 1_000) | 130 | decoded.exp <= Math.floor(now.epochMilliseconds / 1_000) |
| 110 | ) { | 131 | ) { |
| 111 | return yield* new EnvironmentPublishSignatureExpired({ | 132 | return yield* new EnvironmentPublishSignatureExpired({ |
| | 133 | environmentId: input.environmentId, |
| | 134 | threadId: input.threadId, |
| 112 | expiresAt: DateTime.formatIso(DateTime.makeUnsafe(decoded.exp * 1_000)), | 135 | expiresAt: DateTime.formatIso(DateTime.makeUnsafe(decoded.exp * 1_000)), |
| 113 | }); | 136 | }); |
| 114 | } | 137 | } |
| ... | @@ -122,7 +145,14 @@ const make = Effect.gen(function* () { | ... | @@ -122,7 +145,14 @@ const make = Effect.gen(function* () { |
| 122 | }).pipe( | 145 | }).pipe( |
| 123 | Effect.flatMap(decodeProof), | 146 | Effect.flatMap(decodeProof), |
| 124 | Effect.mapError( | 147 | Effect.mapError( |
| 125 | () => new EnvironmentPublishSignatureInvalid({ environmentId: input.environmentId }), | 148 | (cause) => |
| | 149 | new EnvironmentPublishSignatureInvalid({ |
| | 150 | environmentId: input.environmentId, |
| | 151 | threadId: input.threadId, |
| | 152 | reason: "invalid_signature_or_payload", |
| | 153 | stage: "verify_proof", |
| | 154 | cause, |
| | 155 | }), |
| 126 | ), | 156 | ), |
| 127 | ); | 157 | ); |
| 128 | if ( | 158 | if ( |
| ... | @@ -136,12 +166,18 @@ const make = Effect.gen(function* () { | ... | @@ -136,12 +166,18 @@ const make = Effect.gen(function* () { |
| 136 | ) { | 166 | ) { |
| 137 | return yield* new EnvironmentPublishSignatureInvalid({ | 167 | return yield* new EnvironmentPublishSignatureInvalid({ |
| 138 | environmentId: input.environmentId, | 168 | environmentId: input.environmentId, |
| | 169 | threadId: input.threadId, |
| | 170 | reason: "invalid_signature_or_payload", |
| | 171 | stage: "validate_claims", |
| 139 | }); | 172 | }); |
| 140 | } | 173 | } |
| 141 | const expiresAt = DateTime.make(proof.exp * 1_000); | 174 | const expiresAt = DateTime.make(proof.exp * 1_000); |
| 142 | if (expiresAt._tag === "None") { | 175 | if (expiresAt._tag === "None") { |
| 143 | return yield* new EnvironmentPublishSignatureInvalid({ | 176 | return yield* new EnvironmentPublishSignatureInvalid({ |
| 144 | environmentId: input.environmentId, | 177 | environmentId: input.environmentId, |
| | 178 | threadId: input.threadId, |
| | 179 | reason: "invalid_signature_or_payload", |
| | 180 | stage: "validate_expiration", |
| 145 | }); | 181 | }); |
| 146 | } | 182 | } |
| 147 | const thumbprint = yield* crypto | 183 | const thumbprint = yield* crypto |
| ... | @@ -155,7 +191,14 @@ const make = Effect.gen(function* () { | ... | @@ -155,7 +191,14 @@ const make = Effect.gen(function* () { |
| 155 | .pipe( | 191 | .pipe( |
| 156 | Effect.map(formatEnvironmentPublishReplayThumbprint), | 192 | Effect.map(formatEnvironmentPublishReplayThumbprint), |
| 157 | Effect.mapError( | 193 | Effect.mapError( |
| 158 | () => new EnvironmentPublishSignatureInvalid({ environmentId: input.environmentId }), | 194 | (cause) => |
| | 195 | new EnvironmentPublishSignatureInvalid({ |
| | 196 | environmentId: input.environmentId, |
| | 197 | threadId: input.threadId, |
| | 198 | reason: "invalid_signature_or_payload", |
| | 199 | stage: "generate_replay_thumbprint", |
| | 200 | cause, |
| | 201 | }), |
| 159 | ), | 202 | ), |
| 160 | ); | 203 | ); |
| 161 | const consumedNonce = yield* proofReplay.consume({ | 204 | const consumedNonce = yield* proofReplay.consume({ |
| ... | @@ -167,6 +210,9 @@ const make = Effect.gen(function* () { | ... | @@ -167,6 +210,9 @@ const make = Effect.gen(function* () { |
| 167 | if (!consumedNonce) { | 210 | if (!consumedNonce) { |
| 168 | return yield* new EnvironmentPublishSignatureInvalid({ | 211 | return yield* new EnvironmentPublishSignatureInvalid({ |
| 169 | environmentId: input.environmentId, | 212 | environmentId: input.environmentId, |
| | 213 | threadId: input.threadId, |
| | 214 | reason: "replayed_nonce", |
| | 215 | stage: "consume_nonce", |
| 170 | }); | 216 | }); |
| 171 | } | 217 | } |
| 172 | }), | 218 | }), |