-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* testing * a * a * wip * let it cook * working would be nice * Microsoft sukz * lol * Install SST Ion in CI * ? * wat * debug * a * fix? * a * Disable Cloudflare provider for now * Azure OIDC is sooo bad * env properly? * invalid action * whoops * pulumi via ion (#294) * pulumi via ion * format * remove release workflow * give provider creds to sst * cd infra * tailscale secrets * i used wrong stage -_- * actually right stage * use sst secrets * fix deploy * fix userData * remove vercel workflows --------- Co-authored-by: Oscar Beaumont <[email protected]> * hardcode client env vars * only run sst on main branch * DATABASE_URL * VITE_PROD_URL --------- Co-authored-by: Brendan Allan <[email protected]>
- Loading branch information
1 parent
d5aaec5
commit 1f0149c
Showing
18 changed files
with
2,725 additions
and
219 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
name: SST Deploy | ||
on: | ||
push: | ||
branches: [main] | ||
workflow_dispatch: | ||
|
||
concurrency: | ||
group: "merge-${{ github.ref }}" | ||
|
||
permissions: | ||
id-token: write | ||
contents: read | ||
|
||
jobs: | ||
sst: | ||
runs-on: ubuntu-latest | ||
# This is required to workaround the lack of wildcard for OIDC scope | ||
# https://github.com/Azure/azure-workload-identity/issues/373 | ||
# | ||
# I swear to god Microsoft have never tried anything they have built. | ||
environment: production | ||
steps: | ||
- name: Git clone the repository | ||
uses: actions/checkout@v3 | ||
|
||
- name: Configure AWS credentials | ||
uses: aws-actions/configure-aws-credentials@v2 | ||
with: | ||
role-to-assume: "arn:aws:iam::101829795063:role/mattrax-gh-actions" | ||
aws-region: us-east-1 | ||
role-session-name: mattrax-sst-workflow | ||
|
||
- uses: pnpm/action-setup@v2 | ||
with: | ||
version: latest | ||
|
||
- name: Install SST | ||
run: curl -fsSL https://ion.sst.dev/install | bash | ||
|
||
- run: pnpm i | ||
|
||
- run: cd infra && sst deploy --stage brendonovich | ||
env: | ||
ARM_USE_OIDC: true | ||
ARM_CLIENT_ID: 8aaf4dbe-001d-4003-9572-8dd6ab658c53 | ||
ARM_TENANT_ID: 3509b545-2799-4c5c-a0d2-f822ddbd416c | ||
AZURE_SUBSCRIPTION_ID: 3509b545-2799-4c5c-a0d2-f822ddbd416c | ||
CLOUDFLARE_DEFAULT_ACCOUNT_ID: f02b3ef168fe64129e9941b4fb2e4dc1 | ||
CLOUDFLARE_EMAIL: [email protected] | ||
CLOUDFLARE_API_KEY: ${{ secrets.CLOUDFLARE_API_KEY }} | ||
OAUTH_CLIENT_ID: kXdvzkEgiN11CNTRL | ||
OAUTH_CLIENT_SECRET: ${{ secrets.TAILSCALE_OAUTH_SECRET }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
import type { NextRequest } from "next/server"; | ||
// @ts-expect-error | ||
import { createEnv } from "@t3-oss/env-core"; | ||
import { render } from "@react-email/render"; | ||
import { AwsClient } from "aws4fetch"; | ||
import { z } from "zod"; | ||
|
||
import { TenantAdminInviteEmail, LoginCodeEmail } from "~/emails"; | ||
import UserEnrollmentInvite from "~/emails/UserEnrollmentInvite"; | ||
|
||
function optional_in_dev<T extends z.ZodTypeAny>( | ||
schema: T, | ||
): z.ZodOptional<T> | T { | ||
return process.env.NODE_ENV === "development" ? schema.optional() : schema; | ||
} | ||
|
||
const env = createEnv({ | ||
server: { | ||
// Emails and other AWS services | ||
// Get these values from the output of the Cloudformation template | ||
AWS_ACCESS_KEY_ID: optional_in_dev(z.string()), | ||
AWS_SECRET_ACCESS_KEY: optional_in_dev(z.string()), | ||
FROM_ADDRESS: z.string(), | ||
INTERNAL_SECRET: z.string(), | ||
}, | ||
runtimeEnv: process.env, | ||
emptyStringAsUndefined: true, | ||
}); | ||
|
||
const aws = | ||
env.AWS_ACCESS_KEY_ID && env.AWS_SECRET_ACCESS_KEY | ||
? new AwsClient({ | ||
region: "us-east-1", | ||
accessKeyId: env.AWS_ACCESS_KEY_ID, | ||
secretAccessKey: env.AWS_SECRET_ACCESS_KEY, | ||
}) | ||
: undefined; | ||
|
||
const REQUEST_SCHEMA = z | ||
.object({ | ||
to: z.string(), | ||
subject: z.string(), | ||
}) | ||
.and( | ||
z.union([ | ||
z.object({ | ||
type: z.literal("tenantAdminInvite"), | ||
invitedByEmail: z.string(), | ||
tenantName: z.string(), | ||
inviteLink: z.string(), | ||
}), | ||
z.object({ | ||
type: z.literal("loginCode"), | ||
code: z.string(), | ||
}), | ||
z.object({ | ||
type: z.literal("userEnrollmentInvite"), | ||
tenantName: z.string(), | ||
}), | ||
]), | ||
); | ||
|
||
export type RequestSchema = z.infer<typeof REQUEST_SCHEMA>; | ||
|
||
export async function POST(req: NextRequest) { | ||
const auth = req.headers.get("authorization"); | ||
if (auth !== env.INTERNAL_SECRET) { | ||
return new Response("Unauthorized", { status: 401 }); | ||
} | ||
|
||
const args = REQUEST_SCHEMA.parse(await req.json()); | ||
|
||
if (env.FROM_ADDRESS === "console") { | ||
console.log("SEND EMAIL", args); | ||
return; | ||
} | ||
|
||
if (!aws) { | ||
const msg = "AWS client not setup but 'FROM_ADDRESS' provided!"; | ||
console.error(msg); | ||
throw new Error(msg); | ||
} | ||
|
||
let component: JSX.Element; | ||
|
||
if (args.type === "tenantAdminInvite") { | ||
component = TenantAdminInviteEmail(args); | ||
} else if (args.type === "loginCode") { | ||
component = LoginCodeEmail(args); | ||
} else if (args.type === "userEnrollmentInvite") { | ||
component = UserEnrollmentInvite(args); | ||
} else { | ||
throw new Error(`Unknown email type`); | ||
} | ||
|
||
const emailHtml = render(component); | ||
const resp = await aws.fetch( | ||
"https://email.us-east-1.amazonaws.com/v2/email/outbound-emails", | ||
{ | ||
method: "POST", | ||
headers: { "Content-Type": "application/json" }, | ||
body: JSON.stringify({ | ||
FromEmailAddress: env.FROM_ADDRESS, | ||
Destination: { | ||
ToAddresses: [args.to], | ||
}, | ||
Content: { | ||
Simple: { | ||
Body: { | ||
Html: { | ||
Charset: "UTF-8", | ||
Data: emailHtml, | ||
}, | ||
}, | ||
Subject: { | ||
Charset: "UTF-8", | ||
Data: args.subject, | ||
}, | ||
}, | ||
}, | ||
}), | ||
}, | ||
); | ||
if (!resp.ok) { | ||
console.error( | ||
`Error sending email with SES status '${ | ||
resp.status | ||
}': ${await resp.text()}`, | ||
); | ||
throw new Error("Failed to send email."); | ||
} | ||
|
||
return new Response("Email sent", { status: 200 }); | ||
} |
Oops, something went wrong.