(state: string)
| 22 | |
| 23 | /** |
| 24 | * Signing key for the install state. |
| 25 | * |
| 26 | * `BETTER_AUTH_SECRET` is read here rather than through `env.ts` because better-auth owns the |
| 27 | * variable and validates it at startup; this module only borrows it. |
| 28 | */ |
| 29 | function getKey(): Uint8Array { |
| 30 | const secret = process.env.BETTER_AUTH_SECRET; |
| 31 | if (secret == null) throw new Error("BETTER_AUTH_SECRET is not set"); |
| 32 | return new TextEncoder().encode(secret); |
| 33 | } |
| 34 | |
| 35 | /** |
| 36 | * Opaque, tamper-evident proof that WE started an install for a specific organization, carried |
| 37 | * through GitHub and handed back on the callback. |
| 38 | * |
| 39 | * A signed JWT via `jose` rather than hand-rolled `base64url(payload).hmac`: the previous version |
| 40 | * open-coded the signature comparison, the expiry check and the purpose tag, and `jose` does all |
| 41 | * three - including pinning the algorithm on verify, which is the footgun that makes hand-rolled |
| 42 | * token code worth avoiding. |
| 43 | * |
| 44 | * NOT encrypted - the payload is readable by anyone holding the link, which is fine because it |
| 45 | * only names an organization the holder is already installing for. It is also NOT authorization to |
| 46 | * bind any particular installation: state cannot name one, because it is minted before the |
| 47 | * installation exists. See `handleInstallation` for what actually gates the bind. |
| 48 | */ |
| 49 | export async function createInstallState(organizationId: string, returnPath?: string): Promise<string> { |
| 50 | return await new SignJWT({ organizationId, returnPath }) |
no test coverage detected