| ... | ... | @@ -1,5 +1,6 @@ |
| 1 | 1 | import { |
| 2 | 2 | RelayAgentActivityPublishProofPayload, |
| 3 | RelayAgentActivityPublishProofInvalidReason, |
| 3 | 4 | type RelayAgentActivityPublishRequest, |
| 4 | 5 | } from "@t3tools/contracts/relay"; |
| 5 | 6 | import { |
| ... | ... | @@ -23,11 +24,13 @@ import * as RelayConfiguration from "../Config.ts"; |
| 23 | 24 | export class EnvironmentPublishSignatureExpired extends Schema.TaggedErrorClass<EnvironmentPublishSignatureExpired>()( |
| 24 | 25 | "EnvironmentPublishSignatureExpired", |
| 25 | 26 | { |
| 27 | environmentId: Schema.String, |
| 28 | threadId: Schema.String, |
| 26 | 29 | expiresAt: Schema.String, |
| 27 | 30 | }, |
| 28 | 31 | ) { |
| 29 | 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 | 38 | "EnvironmentPublishSignatureInvalid", |
| 36 | 39 | { |
| 37 | 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 | 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 | 116 | const now = yield* DateTime.now; |
| 103 | 117 | const decoded = yield* Effect.try({ |
| 104 | 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 | 128 | if ( |
| 108 | 129 | typeof decoded.exp === "number" && |
| 109 | 130 | decoded.exp <= Math.floor(now.epochMilliseconds / 1_000) |
| 110 | 131 | ) { |
| 111 | 132 | return yield* new EnvironmentPublishSignatureExpired({ |
| 133 | environmentId: input.environmentId, |
| 134 | threadId: input.threadId, |
| 112 | 135 | expiresAt: DateTime.formatIso(DateTime.makeUnsafe(decoded.exp * 1_000)), |
| 113 | 136 | }); |
| 114 | 137 | } |
| ... | ... | @@ -122,7 +145,14 @@ const make = Effect.gen(function* () { |
| 122 | 145 | }).pipe( |
| 123 | 146 | Effect.flatMap(decodeProof), |
| 124 | 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 | 158 | if ( |
| ... | ... | @@ -136,12 +166,18 @@ const make = Effect.gen(function* () { |
| 136 | 166 | ) { |
| 137 | 167 | return yield* new EnvironmentPublishSignatureInvalid({ |
| 138 | 168 | environmentId: input.environmentId, |
| 169 | threadId: input.threadId, |
| 170 | reason: "invalid_signature_or_payload", |
| 171 | stage: "validate_claims", |
| 139 | 172 | }); |
| 140 | 173 | } |
| 141 | 174 | const expiresAt = DateTime.make(proof.exp * 1_000); |
| 142 | 175 | if (expiresAt._tag === "None") { |
| 143 | 176 | return yield* new EnvironmentPublishSignatureInvalid({ |
| 144 | 177 | environmentId: input.environmentId, |
| 178 | threadId: input.threadId, |
| 179 | reason: "invalid_signature_or_payload", |
| 180 | stage: "validate_expiration", |
| 145 | 181 | }); |
| 146 | 182 | } |
| 147 | 183 | const thumbprint = yield* crypto |
| ... | ... | @@ -155,7 +191,14 @@ const make = Effect.gen(function* () { |
| 155 | 191 | .pipe( |
| 156 | 192 | Effect.map(formatEnvironmentPublishReplayThumbprint), |
| 157 | 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 | 204 | const consumedNonce = yield* proofReplay.consume({ |
| ... | ... | @@ -167,6 +210,9 @@ const make = Effect.gen(function* () { |
| 167 | 210 | if (!consumedNonce) { |
| 168 | 211 | return yield* new EnvironmentPublishSignatureInvalid({ |
| 169 | 212 | environmentId: input.environmentId, |
| 213 | threadId: input.threadId, |
| 214 | reason: "replayed_nonce", |
| 215 | stage: "consume_nonce", |
| 170 | 216 | }); |
| 171 | 217 | } |
| 172 | 218 | }), |