feat: Add auth flow (#146)

This adds a simple way to incorporate a centralized authentication flow.

The idea is to have the user, API and SSH (for machine authentication)
all in one place using `openauthjs` + `SST`

We also have a database now :)

> We are using InstantDB as it allows us to authenticate a use with just
the email. Plus it is super simple simple to use _of course after the
initial fumbles trying to design the db and relationships_
This commit is contained in:
Wanjohi
2025-01-04 00:02:28 +03:00
committed by GitHub
parent 33895974a7
commit fc5a755408
136 changed files with 3512 additions and 1914 deletions

View File

@@ -1,87 +0,0 @@
# How to Deploy Your Own MoQ Relay on a Server
This guide will walk you through the steps to deploy your own MoQ relay on a server.
## Prerequisites
1. **Server Requirements:**
- Ensure port 443 is open for both TCP and UDP (`:443/udp & :443/tcp`).
- The server should have a minimum of **4GB RAM** and **2 vCPUs**.
- Supports ARM or AMD64 architecture.
2. **Software Requirements:**
- Docker and `docker-compose` must be installed on the server. You can use [this installation script](https://github.com/docker/docker-install) for Docker.
- Git must be installed to clone the necessary repository.
3. **Certificates:**
- You will need private and public certificates. It is recommended to use certificates from a trusted CA rather than self-signed certificates.
## Installation Steps
### Step 1: Clone the Repository
Clone the `kixelated/moq-rs` repository to your local machine:
```bash
git clone https://github.com/kixelated/moq-rs moq
```
### Step 2: Verify Port Availability
Check if port 443 is already in use on your server:
```bash
sudo netstat -tulpn | grep ':443' | grep LISTEN
```
or
```bash
sudo lsof -i -P -n | grep LISTEN | grep 443
```
If you find any processes using port 443, consider terminating them.
### Step 3: Configure Ports
Navigate to the cloned directory and edit the Docker compose file to use port 443:
```bash
cd moq
vim docker-compose.yml
```
Change the ports section from lines 34 to 35 to:
```yaml
ports:
- "443:443"
- "443:443/udp"
```
### Step 4: Prepare Certificates
Copy your generated certificates into the `moq/dev` directory and rename them:
```bash
cp cert.pem moq/dev/localhost.crt
cp key.pem moq/dev/localhost.key
```
### Step 5: Start Docker Instances
Ensure you are in the root directory of the `moq` project, then start the Docker containers:
```bash
docker compose up -d
```
### Step 6: Link Domain to Server IP
Configure your DNS settings to connect your server's IP address to your domain:
```
Record Type: A
Subdomain: relay.fst.so
IP Address: xx.xxx.xx.xxx
```
Congratulations, your MoQ server is now set up! You can verify its functionality by using the [MoQ Checker](https://nestri.pages.dev/moq/checker).

View File

@@ -1,22 +1,52 @@
import { isPermanentStage } from "./stage";
import { domain } from "./dns";
import { secret } from "./secrets"
//TODO: Use this instead of wrangler
// export const api = new sst.cloudflare.Worker("apiApi", {
// url: true,
// handler: "packages/api/src/index.ts",
// // live: true,
// });
sst.Linkable.wrap(random.RandomString, (resource) => ({
properties: {
value: resource.result,
},
}));
if (!isPermanentStage) {
new sst.x.DevCommand("apiDev", {
dev: {
command: "bun run dev",
directory: "packages/api",
autostart: true,
},
})
}
export const authFingerprintKey = new random.RandomString(
"AuthFingerprintKey",
{
length: 32,
},
);
// export const outputs = {
// api: api.url
// }
export const urls = new sst.Linkable("Urls", {
properties: {
api: "https://api." + domain,
auth: "https://auth." + domain,
},
});
export const kv = new sst.cloudflare.Kv("CloudflareAuthKV")
export const auth = new sst.cloudflare.Worker("Auth", {
link: [
kv,
urls,
authFingerprintKey,
secret.InstantAdminToken,
secret.InstantAppId,
secret.LoopsApiKey
],
handler: "./packages/functions/src/auth.ts",
url: true,
domain: "auth." + domain
});
export const api = new sst.cloudflare.Worker("Api", {
link: [
urls,
],
url: true,
handler: "./packages/functions/src/api/index.ts",
domain: "api." + domain
})
export const outputs = {
auth: auth.url,
api: api.url
}

10
infra/cli.ts Normal file
View File

@@ -0,0 +1,10 @@
import { auth, urls } from "./api"
// export const cmd = new sst.x.DevCommand("Cmd", {
// link: [urls, auth],
// dev: {
// autostart: true,
// command: "cd packages/cmd && go run main.go"
// }
// })

9
infra/dns.ts Normal file
View File

@@ -0,0 +1,9 @@
export const domain =
{
production: "prod.nestri.io", //temporary use until we go into the real production
dev: "dev.nestri.io",
}[$app.stage] || $app.stage + ".dev.nestri.io";
export const zone = cloudflare.getZoneOutput({
name: "nestri.io",
});

View File

@@ -1,9 +0,0 @@
// export const domain =
// {
// production: "fst.so",
// dev: "dev.fst.so",
// }[$app.stage] || $app.stage + ".dev.fst.so";
// export const zone = cloudflare.getZoneOutput({
// name: "fst.so",
// });

View File

@@ -1,22 +0,0 @@
import { resolve as pathResolve } from "node:path";
import { readFileSync as readFile } from "node:fs";
//Copy your (known) ssh public key to the remote machine
//ssh-copy-id "-p $port" user@host
const domain = "fst.so"
const ips = ["95.216.29.238"]
// Get the hosted zone
const zone = aws.route53.getZone({ name: domain });
// Create an A record
const record = new aws.route53.Record("Relay DNS Records", {
zoneId: zone.then(zone => zone.zoneId),
type: "A",
name: `relay.${domain}`,
ttl: 300,
records: ips,
});
// Export the URL
export const url = $interpolate`https://${record.name}`;

View File

@@ -1,5 +1,7 @@
export const secret = {
CloudflareAccountIdSecret: new sst.Secret("CloudflareAccountId"),
};
export const allSecrets = Object.values(secret);
InstantAdminToken: new sst.Secret("InstantAdminToken"),
InstantAppId: new sst.Secret("InstantAppId"),
LoopsApiKey: new sst.Secret("LoopsApiKey")
};
export const allSecrets = Object.values(secret);

View File

@@ -1,79 +0,0 @@
//Deploys the website to cloudflare pages under the domain nestri.io (redirects all requests to www.nestri.io to avoid duplicate content)
import { isPermanentStage } from "./stage";
export const www = new cloudflare.PagesProject("www", {
name: "nestri",
accountId: "8405b2acb6746935b975bc2cfcb5c288",
productionBranch: "main",
buildConfig: {
rootDir: "apps/www",
buildCommand: "bun run build",
destinationDir: "dist"
},
deploymentConfigs: {
production: {
compatibilityFlags: ["nodejs_compat"]
},
preview: {
compatibilityFlags: ["nodejs_compat"]
}
},
source: {
type: "github",
config: {
owner: "nestriness",
deploymentsEnabled: true,
productionBranch: "main",
repoName: "nestri",
productionDeploymentEnabled: true,
prCommentsEnabled: true,
}
}
});
//TODO: Maybe handle building Qwik ourselves? This prevents us from relying on CF too much, we are open-source anyway 🤷🏾‍♂️
//TODO: Add a local dev server for Qwik that can be linked with whatever we want
//TODO: Link the www PageRule with whatever we give to the local dev server
if (!isPermanentStage) {
new sst.x.DevCommand("www", {
dev: {
command: "bun run dev",
directory: "apps/www",
autostart: true,
},
})
}
// //This creates a resource that can be accessed by itself
// new sst.Linkable.wrap(cloudflare.PageRule, (resource) => ({
// // these properties will be available when linked
// properties: {
// arn: resource.urn
// }
// }))
// //And then you call your linkable resource like this:
// // const www = cloudflare.PageRule("www", {})
// //this creates a linkable resource that can be linked to other resources
// export const linkable2 = new sst.Linkable("ExistingResource", {
// properties: {
// arn: "arn:aws:s3:::nestri-website-artifacts-prod-nestri-io-01h70zg50qz5z"
// },
// include: [
// sst.aws.permission({
// actions: ["s3:*"],
// resources: ["arn:aws:s3:::nestri-website-artifacts-prod-nestri-io-01h70zg50qz5z"]
// }),
// sst.cloudflare.binding({
// type: "r2BucketBindings",
// properties: {
// bucketName: "nestri-website-artifacts-prod-nestri-io-01h70zg50qz5z",
// }
// })
// ]
// })
export const outputs = {
www: www.subdomain,
};