Skip to content

Commit

Permalink
SST Ion (#231)
Browse files Browse the repository at this point in the history
* 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
oscartbeaumont and Brendonovich authored May 17, 2024
1 parent d5aaec5 commit 1f0149c
Show file tree
Hide file tree
Showing 18 changed files with 2,725 additions and 219 deletions.
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

# Used for landing only
VITE_MATTRAX_CLOUD_ORIGIN=http://localhost:3000 # Should point to `apps/web`
VITE_MATTRAX_CLOUD_ORIGIN=http://localhost:3000 # Should point to `apps/web`
26 changes: 0 additions & 26 deletions .github/workflows/aws-ci.yaml

This file was deleted.

33 changes: 0 additions & 33 deletions .github/workflows/aws-deploy.yaml

This file was deleted.

52 changes: 52 additions & 0 deletions .github/workflows/sst.yaml
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 }}
135 changes: 135 additions & 0 deletions apps/email/app/route.ts
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()),
// Email
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 });
}
Loading

0 comments on commit 1f0149c

Please sign in to comment.