mirror of
https://github.com/CompassConnections/Compass.git
synced 2026-04-04 06:51:45 -04:00
Restrict internal/send-search-notifications with API key
This commit is contained in:
@@ -8,26 +8,31 @@ It runs in a docker inside a Google Cloud virtual machine.
|
||||
You must have the `gcloud` CLI.
|
||||
|
||||
On MacOS:
|
||||
|
||||
```bash
|
||||
brew install --cask google-cloud-sdk
|
||||
```
|
||||
|
||||
On Linux:
|
||||
|
||||
```bash
|
||||
sudo apt-get update && sudo apt-get install google-cloud-sdk
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
gcloud init
|
||||
gcloud auth login
|
||||
gcloud config set project YOUR_PROJECT_ID
|
||||
```
|
||||
|
||||
### Setup
|
||||
|
||||
This section is only for the people who are creating a server from scratch, for instance for a forked project.
|
||||
|
||||
One-time commands you may need to run:
|
||||
|
||||
```bash
|
||||
gcloud artifacts repositories create builds \
|
||||
--repository-format=docker \
|
||||
@@ -51,6 +56,20 @@ gcloud projects add-iam-policy-binding compass-130ba \
|
||||
gcloud run services list
|
||||
```
|
||||
|
||||
Set up the saved search notifications job:
|
||||
|
||||
```bash
|
||||
gcloud scheduler jobs create http daily-saved-search-notifications \
|
||||
--schedule="0 19 * * *" \
|
||||
--uri="https://api.compassmeet.com/internal/send-search-notifications" \
|
||||
--http-method=POST \
|
||||
--headers="x-api-key=<API_KEY>" \
|
||||
--time-zone="UTC" \
|
||||
--location=us-west1
|
||||
```
|
||||
|
||||
View it [here](https://console.cloud.google.com/cloudscheduler).
|
||||
|
||||
##### DNS
|
||||
|
||||
* After deployment, Terraform assigns a static external IP to this resource.
|
||||
@@ -60,6 +79,7 @@ gcloud run services list
|
||||
gcloud compute addresses describe api-lb-ip-2 --global --format="get(address)"
|
||||
34.117.20.215
|
||||
```
|
||||
|
||||
Since Vercel manages your domain (`compassmeet.com`):
|
||||
|
||||
1. Log in to [Vercel dashboard](https://vercel.com/dashboard).
|
||||
@@ -67,7 +87,7 @@ Since Vercel manages your domain (`compassmeet.com`):
|
||||
3. Add an **A record** for your API subdomain:
|
||||
|
||||
| Type | Name | Value | TTL |
|
||||
| ---- | ---- | ------------ | ----- |
|
||||
|------|------|--------------|-------|
|
||||
| A | api | 34.123.45.67 | 600 s |
|
||||
|
||||
* `Name` is just the subdomain: `api` → `api.compassmeet.com`.
|
||||
@@ -85,7 +105,6 @@ curl -I https://api.compassmeet.com
|
||||
* `nslookup` should return the LB IP (`34.123.45.67`).
|
||||
* `curl -I` should return `200 OK` from your service.
|
||||
|
||||
|
||||
If SSL isn’t ready (may take 15 mins), check LB logs:
|
||||
|
||||
```bash
|
||||
@@ -96,7 +115,9 @@ gcloud compute ssl-certificates describe api-lb-cert-2
|
||||
|
||||
Secrets are strings that shouldn't be checked into Git (eg API keys, passwords).
|
||||
|
||||
Add the secrets for your specific project in [Google Cloud Secrets manager](https://console.cloud.google.com/security/secret-manager), so that the virtual machine can access them.
|
||||
Add the secrets for your specific project
|
||||
in [Google Cloud Secrets manager](https://console.cloud.google.com/security/secret-manager), so that the virtual machine
|
||||
can access them.
|
||||
|
||||
For Compass, the name of the secrets are in [secrets.ts](../../common/src/secrets.ts).
|
||||
|
||||
@@ -111,13 +132,16 @@ In root directory, run the local api with hot reload, along with all the other b
|
||||
### Deploy
|
||||
|
||||
Run in this directory to deploy your code to the server.
|
||||
|
||||
```bash
|
||||
./deploy-api.sh prod
|
||||
```
|
||||
|
||||
### Connect to the server
|
||||
|
||||
Run in this directory to connect to the API server running as virtual machine in Google Cloud. You can access logs, files, debug, etc.
|
||||
Run in this directory to connect to the API server running as virtual machine in Google Cloud. You can access logs,
|
||||
files, debug, etc.
|
||||
|
||||
```bash
|
||||
./ssh-api.sh prod
|
||||
```
|
||||
@@ -130,5 +154,4 @@ sudo docker logs -f $(sudo docker ps -alq)
|
||||
docker exec -it $(sudo docker ps -alq) sh
|
||||
docker run -it --rm $(docker images -q | head -n 1) sh
|
||||
docker rmi -f $(docker images -aq)
|
||||
|
||||
```
|
||||
|
||||
@@ -23,7 +23,6 @@ import {getDisplayUser, getUser} from './get-user'
|
||||
import {getMe} from './get-me'
|
||||
import {hasFreeLike} from './has-free-like'
|
||||
import {health} from './health'
|
||||
import {sendSearchNotifications} from './send-search-notifications'
|
||||
import {type APIHandler, typedEndpoint} from './helpers/endpoint'
|
||||
import {hideComment} from './hide-comment'
|
||||
import {likeLover} from './like-lover'
|
||||
@@ -53,6 +52,7 @@ import {getNotifications} from './get-notifications'
|
||||
import {updateNotifSettings} from './update-notif-setting'
|
||||
import swaggerUi from "swagger-ui-express"
|
||||
import * as fs from "fs"
|
||||
import {sendSearchNotifications} from "api/send-search-notifications";
|
||||
|
||||
const allowCorsUnrestricted: RequestHandler = cors({})
|
||||
|
||||
@@ -166,7 +166,6 @@ const handlers: { [k in APIPath]: APIHandler<k> } = {
|
||||
'get-channel-messages': getChannelMessages,
|
||||
'get-channel-seen-time': getLastSeenChannelTime,
|
||||
'set-channel-seen-time': setChannelLastSeenTime,
|
||||
'send-search-notifications': sendSearchNotifications,
|
||||
}
|
||||
|
||||
Object.entries(handlers).forEach(([path, handler]) => {
|
||||
@@ -194,6 +193,28 @@ Object.entries(handlers).forEach(([path, handler]) => {
|
||||
}
|
||||
})
|
||||
|
||||
console.log('COMPASS_API_KEY:', process.env.COMPASS_API_KEY)
|
||||
|
||||
// Internal Endpoints
|
||||
app.post(
|
||||
'/' + pathWithPrefix("internal/send-search-notifications"),
|
||||
async (req, res) => {
|
||||
const apiKey = req.header("x-api-key");
|
||||
if (apiKey !== process.env.COMPASS_API_KEY) {
|
||||
return res.status(401).json({error: "Unauthorized"});
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await sendSearchNotifications()
|
||||
return res.status(200).json(result)
|
||||
} catch (err) {
|
||||
console.error("Failed to send notifications:", err);
|
||||
return res.status(500).json({error: "Internal server error"});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
app.use(allowCorsUnrestricted, (req, res) => {
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.status(200).send()
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import {APIHandler} from './helpers/endpoint'
|
||||
import {createSupabaseDirectClient} from "shared/supabase/init";
|
||||
import {from, renderSql, select} from "shared/supabase/sql-builder";
|
||||
import {loadProfiles, profileQueryType} from "api/get-profiles";
|
||||
@@ -18,7 +17,7 @@ export const notifyBookmarkedSearch = async (matches: MatchesByUserType) => {
|
||||
}
|
||||
}
|
||||
|
||||
export const sendSearchNotifications: APIHandler<'send-search-notifications'> = async (_, auth) => {
|
||||
export const sendSearchNotifications = async () => {
|
||||
const pg = createSupabaseDirectClient()
|
||||
|
||||
const search_query = renderSql(
|
||||
|
||||
@@ -51,14 +51,6 @@ export const API = (_apiTypeCheck = {
|
||||
props: z.object({}),
|
||||
returns: {} as { jwt: string },
|
||||
},
|
||||
'send-search-notifications': {
|
||||
method: 'POST',
|
||||
authed: false,
|
||||
props: z.object({}),
|
||||
returns: {} as {
|
||||
status: 'success' | 'fail'
|
||||
},
|
||||
},
|
||||
'mark-all-notifs-read': {
|
||||
method: 'POST',
|
||||
authed: true,
|
||||
|
||||
@@ -20,7 +20,7 @@ export class APIError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
export function pathWithPrefix(path: APIPath) {
|
||||
export function pathWithPrefix(path: string) {
|
||||
return `v0/${path}`
|
||||
}
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export const secrets = (
|
||||
'TEST_CREATE_USER_KEY',
|
||||
'GEODB_API_KEY',
|
||||
'RESEND_KEY',
|
||||
'COMPASS_API_KEY',
|
||||
'NEXT_PUBLIC_FIREBASE_API_KEY',
|
||||
// Some typescript voodoo to keep the string literal types while being not readonly.
|
||||
] as const
|
||||
|
||||
@@ -67,6 +67,7 @@ export async function baseApiCall(props: {
|
||||
body:
|
||||
params == null || method === 'GET' ? undefined : JSON.stringify(params),
|
||||
})
|
||||
// console.log(req)
|
||||
return fetch(req).then(async (resp) => {
|
||||
const json = (await resp.json()) as { [k: string]: any }
|
||||
if (!resp.ok) {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
#!/bin/bash
|
||||
|
||||
curl -X POST http://localhost:8088/v0/send-search-notifications
|
||||
set -e
|
||||
cd "$(dirname "$0")"/..
|
||||
|
||||
source .env
|
||||
|
||||
#export url=http://localhost:8088/v0
|
||||
export url=https://api.compassmeet.com
|
||||
|
||||
export endpoint=/internal/send-search-notifications
|
||||
|
||||
curl -X POST ${url}${endpoint} \
|
||||
-H "x-api-key: ${COMPASS_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{}'
|
||||
|
||||
echo
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user