Merge branch 'main' into shell-updates
This commit is contained in:
commit
db8a3d1ee0
132 changed files with 3609 additions and 5150 deletions
5
.dockerignore
Normal file
5
.dockerignore
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
out
|
||||||
|
signald
|
||||||
|
docker-compose.yml
|
||||||
|
README.md
|
||||||
18
.editorconfig
Normal file
18
.editorconfig
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
indent_size = 2
|
||||||
|
indent_style = space
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = 0
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
|
[{Makefile,**.mk}]
|
||||||
|
# Use tabs for indentation (Makefiles require tabs)
|
||||||
|
indent_style = tab
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
|
|
@ -5,6 +5,7 @@ build/**
|
||||||
**/dist/**
|
**/dist/**
|
||||||
.next/**
|
.next/**
|
||||||
docker/zammad/addons/**
|
docker/zammad/addons/**
|
||||||
|
!docker/zammad/addons/.gitkeep
|
||||||
.npmrc
|
.npmrc
|
||||||
coverage/
|
coverage/
|
||||||
build/
|
build/
|
||||||
|
|
@ -20,3 +21,6 @@ coverage
|
||||||
.pgpass
|
.pgpass
|
||||||
**/dist/**
|
**/dist/**
|
||||||
.metamigo.local.json
|
.metamigo.local.json
|
||||||
|
out/
|
||||||
|
signald-state/*
|
||||||
|
!./signald-state/.gitkeep
|
||||||
|
|
|
||||||
78
.gitpod.dockerfile
Normal file
78
.gitpod.dockerfile
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
FROM gitpod/workspace-full
|
||||||
|
|
||||||
|
# install tools we need
|
||||||
|
RUN set -ex; \
|
||||||
|
pyenv global system; \
|
||||||
|
sudo add-apt-repository ppa:ansible/ansible; \
|
||||||
|
sudo add-apt-repository ppa:maxmind/ppa; \
|
||||||
|
curl -s https://helm.baltorepo.com/organization/signing.asc | sudo apt-key add - ; \
|
||||||
|
curl -L "https://packages.gitlab.com/install/repositories/runner/gitlab-runner/script.deb.sh" | sudo bash; \
|
||||||
|
echo "deb https://baltocdn.com/helm/stable/debian/ all main" | sudo tee /etc/apt/sources.list.d/helm-stable-debian.list ; \
|
||||||
|
sudo apt-get update; \
|
||||||
|
sudo apt-get -y upgrade ; \
|
||||||
|
sudo apt-get install -y \
|
||||||
|
ansible \
|
||||||
|
build-essential \
|
||||||
|
httpie \
|
||||||
|
fd-find \
|
||||||
|
ffmpeg \
|
||||||
|
geoipupdate \
|
||||||
|
gitlab-runner \
|
||||||
|
helm \
|
||||||
|
htop \
|
||||||
|
iotop \
|
||||||
|
iptraf \
|
||||||
|
jq \
|
||||||
|
kitty-terminfo \
|
||||||
|
libolm-dev \
|
||||||
|
ncdu \
|
||||||
|
postgresql \
|
||||||
|
pwgen \
|
||||||
|
python3-wheel \
|
||||||
|
ripgrep \
|
||||||
|
rsync \
|
||||||
|
scdaemon \
|
||||||
|
socat \
|
||||||
|
tmux \
|
||||||
|
unrar \
|
||||||
|
unzip \
|
||||||
|
vifm \
|
||||||
|
vim \
|
||||||
|
yamllint \
|
||||||
|
zsh \
|
||||||
|
zsh-syntax-highlighting \
|
||||||
|
; sudo rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
RUN set -ex; \
|
||||||
|
brew install \
|
||||||
|
zoxide \
|
||||||
|
fzf;
|
||||||
|
|
||||||
|
# needed for tailscale
|
||||||
|
RUN sudo update-alternatives --set ip6tables /usr/sbin/ip6tables-nft
|
||||||
|
|
||||||
|
# install npm global packages we need
|
||||||
|
RUN set -ex; \
|
||||||
|
npm install -g \
|
||||||
|
standard-version \
|
||||||
|
turbo \
|
||||||
|
;
|
||||||
|
|
||||||
|
# make a place for all our warez
|
||||||
|
RUN sudo mkdir -p /usr/local/bin
|
||||||
|
|
||||||
|
# install AWS' kubectl
|
||||||
|
# from https://docs.aws.amazon.com/eks/latest/userguide/install-kubectl.html
|
||||||
|
ARG KUBECTL_URL="https://amazon-eks.s3.us-west-2.amazonaws.com/1.21.2/2021-07-05/bin/linux/amd64/kubectl"
|
||||||
|
RUN set -ex; \
|
||||||
|
curl -o kubectl "${KUBECTL_URL}"; \
|
||||||
|
chmod +x kubectl; \
|
||||||
|
sudo mv kubectl /usr/local/bin
|
||||||
|
|
||||||
|
# install cloudflared
|
||||||
|
# from https://github.com/cloudflare/cloudflared/releases
|
||||||
|
ARG CLOUDFLARED_VERSION="2023.2.1"
|
||||||
|
RUN set -ex; \
|
||||||
|
wget --progress=dot:mega https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64.deb; \
|
||||||
|
sudo dpkg -i cloudflared-linux-amd64.deb; \
|
||||||
|
cloudflared --version
|
||||||
63
.gitpod.yml
Normal file
63
.gitpod.yml
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
---
|
||||||
|
# build the docker image for our gitpod from this dockerfile
|
||||||
|
image:
|
||||||
|
file: .gitpod.dockerfile
|
||||||
|
# all init+before are run in prebuilds, and on workspace startup
|
||||||
|
tasks:
|
||||||
|
- name: npm install
|
||||||
|
init: |
|
||||||
|
npm install
|
||||||
|
# extra extensions we share
|
||||||
|
vscode:
|
||||||
|
extensions:
|
||||||
|
- redhat.vscode-yaml
|
||||||
|
- ms-azuretools.vscode-docker
|
||||||
|
- ms-kubernetes-tools.vscode-kubernetes-tools
|
||||||
|
- ms-vscode.makefile-tools
|
||||||
|
- bungcip.better-toml
|
||||||
|
- sleistner.vscode-fileutils
|
||||||
|
- esbenp.prettier-vscode
|
||||||
|
- darkriszty.markdown-table-prettify
|
||||||
|
- VisualStudioExptTeam.vscodeintellicode
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- name: Zammad
|
||||||
|
port: 8001
|
||||||
|
onOpen: notify
|
||||||
|
|
||||||
|
- name: Leafcutter Local
|
||||||
|
port: 3001
|
||||||
|
onOpen: notify
|
||||||
|
|
||||||
|
- name: Leafcutter
|
||||||
|
port: 8004
|
||||||
|
onOpen: notify
|
||||||
|
|
||||||
|
- name: Link
|
||||||
|
port: 8003
|
||||||
|
onOpen: notify
|
||||||
|
|
||||||
|
- name: Link Local
|
||||||
|
port: 3000
|
||||||
|
onOpen: notify
|
||||||
|
|
||||||
|
|
||||||
|
- name: Metamigo
|
||||||
|
port: 8002
|
||||||
|
onOpen: notify
|
||||||
|
|
||||||
|
- name: Metamigo Local
|
||||||
|
port: 2999
|
||||||
|
onOpen: notify
|
||||||
|
|
||||||
|
- name: Metamigo API
|
||||||
|
port: 8004
|
||||||
|
onOpen: notify
|
||||||
|
|
||||||
|
- name: Zammad Postgres
|
||||||
|
port: 5432
|
||||||
|
onOpen: notify
|
||||||
|
|
||||||
|
- name: Metamigo Postgres
|
||||||
|
port: 5433
|
||||||
|
onOpen: notify
|
||||||
2
.nvmrc
2
.nvmrc
|
|
@ -1 +1 @@
|
||||||
v20
|
v20.2.0
|
||||||
|
|
|
||||||
14
Makefile
14
Makefile
|
|
@ -58,13 +58,17 @@ setup-signal:
|
||||||
create-admin-user:
|
create-admin-user:
|
||||||
docker exec -i $(shell docker ps -aqf "name=metamigo-postgresql") bash < ./scripts/create-admin-user.sh
|
docker exec -i $(shell docker ps -aqf "name=metamigo-postgresql") bash < ./scripts/create-admin-user.sh
|
||||||
|
|
||||||
start:
|
|
||||||
|
.env:
|
||||||
|
@test -f .env || echo "You must create .env please refer to the README" && exit 1
|
||||||
|
|
||||||
|
start: .env
|
||||||
CURRENT_UID=$(CURRENT_UID) docker-compose up -d
|
CURRENT_UID=$(CURRENT_UID) docker-compose up -d
|
||||||
|
|
||||||
start-dev:
|
start-dev: .env
|
||||||
CURRENT_UID=$(CURRENT_UID) docker-compose up --build -d
|
CURRENT_UID=$(CURRENT_UID) docker-compose up --build -d
|
||||||
|
|
||||||
restart:
|
restart: .env
|
||||||
CURRENT_UID=$(CURRENT_UID) docker restart $(shell docker ps -a -q)
|
CURRENT_UID=$(CURRENT_UID) docker restart $(shell docker ps -a -q)
|
||||||
|
|
||||||
stop:
|
stop:
|
||||||
|
|
@ -73,3 +77,7 @@ stop:
|
||||||
destroy:
|
destroy:
|
||||||
docker-compose down
|
docker-compose down
|
||||||
docker volume prune
|
docker volume prune
|
||||||
|
|
||||||
|
|
||||||
|
dev-metamigo:
|
||||||
|
CURRENT_UID=$(CURRENT_UID) docker compose up -d metamigo-postgresql signald
|
||||||
|
|
|
||||||
47
README.md
47
README.md
|
|
@ -1,19 +1,36 @@
|
||||||
# Notes
|
# Dev Setup
|
||||||
|
|
||||||
* Turbo https://turbo.build/repo
|
> NOTE: When using Gitpod/Codespaces, use at least 16GB RAM
|
||||||
* Running dev in certain workspaces https://turbo.build/repo/docs/handbook/dev#running-dev-only-in-certain-workspaces
|
|
||||||
* Linting https://turbo.build/repo/docs/handbook/linting
|
|
||||||
* Internal packages https://turbo.build/repo/docs/handbook/sharing-code/internal-packages
|
|
||||||
|
|
||||||
```
|
Local dev with docker-compose
|
||||||
npm i
|
|
||||||
npm ls --production --depth 1 -json | jq -r '.dependencies[].resolved'
|
|
||||||
npm install --workspace=metamigo-common
|
|
||||||
```
|
|
||||||
|
|
||||||
## Todo
|
* Create `link-stack/.env` from Bitwarden `.env for root of link-stack`
|
||||||
|
* Run local dev with docker-compose:
|
||||||
|
```
|
||||||
|
git clone ...
|
||||||
|
cd link-stack
|
||||||
|
make start-dev
|
||||||
|
```
|
||||||
|
|
||||||
* Move the following to be internal packages.
|
Or for local dev of a single app
|
||||||
- [ ] @digiresilence/montar
|
|
||||||
- [ ] @digiresilience/hapi-nextauth
|
* Create `link-stack/apps/link/.env.local` from Bitwarden `.env.local for link-stack/apps/link`
|
||||||
- [ ] @digiresilience/hapi-pg-promise
|
* Create `link-stack/apps/metamigo-frontend/.metamigo.local.json` from Bitwarden `.metamigo.local.json for link-stack/apps/metamigo/frontend`
|
||||||
|
* Build locally for development:
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
make dev-metamigo # this starts the containers
|
||||||
|
npm run migrate # this migrates the db
|
||||||
|
npm run dev:metamigo # this runs metamigo frontend and api
|
||||||
|
```
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
|
||||||
|
- [ ] Delete old JWT config stuff
|
||||||
|
- [ ] Consolidate config
|
||||||
|
- [ ] Complete react-admin upgrade.. make all the metamigo-frontend stuff work
|
||||||
|
* https://marmelab.com/react-admin/Upgrade.html#no-more-prop-injection-in-page-components
|
||||||
|
- [ ] Get metamigo-worker working
|
||||||
|
- [ ] Migrate off mui/styles
|
||||||
|
* https://mui.com/material-ui/migration/v5-style-changes/
|
||||||
|
* the codemods might help us?
|
||||||
|
|
@ -175,7 +175,6 @@ export const Sidebar: FC<SidebarProps> = ({ open, setOpen }) => {
|
||||||
const urgentCount = findOverviewCountByID(7);
|
const urgentCount = findOverviewCountByID(7);
|
||||||
const pendingCount = findOverviewCountByID(3);
|
const pendingCount = findOverviewCountByID(3);
|
||||||
const unassignedCount = findOverviewCountByID(2);
|
const unassignedCount = findOverviewCountByID(2);
|
||||||
console.log({ assignedCount, urgentCount, pendingCount, unassignedCount });
|
|
||||||
|
|
||||||
const logout = () => {
|
const logout = () => {
|
||||||
signOut({ callbackUrl: "/login" });
|
signOut({ callbackUrl: "/login" });
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
FROM node:20-bullseye as builder
|
|
||||||
|
|
||||||
ARG METAMIGO_DIR=/opt/metamigo
|
|
||||||
RUN mkdir -p ${METAMIGO_DIR}/
|
|
||||||
WORKDIR ${METAMIGO_DIR}
|
|
||||||
COPY package.json tsconfig.json ${METAMIGO_DIR}/
|
|
||||||
COPY . ${METAMIGO_DIR}/
|
|
||||||
RUN npm install
|
|
||||||
RUN npm run build
|
|
||||||
# RUN npx --no-install tsc --build --verbose
|
|
||||||
|
|
||||||
RUN rm -Rf ./node_modules
|
|
||||||
|
|
||||||
FROM node:20-bullseye as clean
|
|
||||||
ARG METAMIGO_DIR=/opt/metamigo
|
|
||||||
|
|
||||||
COPY --from=builder ${METAMIGO_DIR} ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
RUN rm -Rf ./node_modules
|
|
||||||
|
|
||||||
FROM node:20-bullseye as pristine
|
|
||||||
LABEL maintainer="Abel Luck <abel@guardianproject.info>"
|
|
||||||
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends --fix-missing \
|
|
||||||
postgresql-client dumb-init ffmpeg
|
|
||||||
|
|
||||||
ARG METAMIGO_DIR=/opt/metamigo
|
|
||||||
ENV METAMIGO_DIR ${METAMIGO_DIR}
|
|
||||||
RUN mkdir -p ${METAMIGO_DIR}
|
|
||||||
RUN chown -R node:node ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
|
||||||
RUN chmod +x /docker-entrypoint.sh
|
|
||||||
|
|
||||||
COPY --from=clean ${METAMIGO_DIR}/ ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
WORKDIR ${METAMIGO_DIR}
|
|
||||||
|
|
||||||
USER node
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
EXPOSE 3001
|
|
||||||
EXPOSE 3002
|
|
||||||
ENV PORT 3000
|
|
||||||
ENV NODE_ENV production
|
|
||||||
|
|
||||||
ARG BUILD_DATE
|
|
||||||
ARG VCS_REF
|
|
||||||
ARG VCS_URL="https://gitlab.com/digiresilience/link/metamigo"
|
|
||||||
ARG VERSION
|
|
||||||
LABEL org.label-schema.schema-version="1.0"
|
|
||||||
LABEL org.label-schema.name="digiresilience.org/link/metamigo"
|
|
||||||
LABEL org.label-schema.description="part of CDR Link"
|
|
||||||
LABEL org.label-schema.build-date=$BUILD_DATE
|
|
||||||
LABEL org.label-schema.vcs-url=$VCS_URL
|
|
||||||
LABEL org.label-schema.vcs-ref=$VCS_REF
|
|
||||||
LABEL org.label-schema.version=$VERSION
|
|
||||||
|
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
set -e
|
|
||||||
|
|
||||||
cd ${AMIGO_DIR}
|
|
||||||
|
|
||||||
if [[ "$1" == "api" ]]; then
|
|
||||||
echo "docker-entrypoint: starting api server"
|
|
||||||
./cli db -- migrate
|
|
||||||
exec dumb-init ./cli api
|
|
||||||
elif [[ "$1" == "worker" ]]; then
|
|
||||||
echo "docker-entrypoint: starting worker"
|
|
||||||
exec dumb-init ./cli worker
|
|
||||||
elif [[ "$1" == "frontend" ]]; then
|
|
||||||
echo "docker-entrypoint: starting frontend"
|
|
||||||
exec dumb-init yarn workspace @app/frontend start
|
|
||||||
elif [[ "$1" == "cli" ]]; then
|
|
||||||
echo "docker-entrypoint: starting frontend"
|
|
||||||
shift 1
|
|
||||||
exec ./cli "$@"
|
|
||||||
else
|
|
||||||
echo "docker-entrypoint: missing argument, one of: api, worker, frontend, cli"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "metamigo-api",
|
"name": "@digiresilience/metamigo-api",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"main": "build/main/cli/index.js",
|
"type": "module",
|
||||||
|
"main": "build/main/main.js",
|
||||||
"author": "Abel Luck <abel@guardianproject.info>",
|
"author": "Abel Luck <abel@guardianproject.info>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -26,8 +27,8 @@
|
||||||
"fluent-ffmpeg": "^2.1.2",
|
"fluent-ffmpeg": "^2.1.2",
|
||||||
"graphile-migrate": "^1.4.1",
|
"graphile-migrate": "^1.4.1",
|
||||||
"graphile-worker": "^0.13.0",
|
"graphile-worker": "^0.13.0",
|
||||||
|
"hapi-auth-bearer-token": "^8.0.0",
|
||||||
"hapi-auth-jwt2": "^10.4.0",
|
"hapi-auth-jwt2": "^10.4.0",
|
||||||
"hapi-postgraphile": "^0.11.0",
|
|
||||||
"hapi-swagger": "^16.0.1",
|
"hapi-swagger": "^16.0.1",
|
||||||
"joi": "^17.9.2",
|
"joi": "^17.9.2",
|
||||||
"jsonwebtoken": "^9.0.0",
|
"jsonwebtoken": "^9.0.0",
|
||||||
|
|
@ -37,6 +38,7 @@
|
||||||
"pg": "^8.11.0",
|
"pg": "^8.11.0",
|
||||||
"pg-monitor": "^2.0.0",
|
"pg-monitor": "^2.0.0",
|
||||||
"pg-promise": "^11.4.3",
|
"pg-promise": "^11.4.3",
|
||||||
|
"postgraphile": "4.12.3",
|
||||||
"postgraphile-plugin-connection-filter": "^2.3.0",
|
"postgraphile-plugin-connection-filter": "^2.3.0",
|
||||||
"remeda": "^1.18.1",
|
"remeda": "^1.18.1",
|
||||||
"twilio": "^4.11.1",
|
"twilio": "^4.11.1",
|
||||||
|
|
@ -53,6 +55,7 @@
|
||||||
"pg-monitor": "^2.0.0",
|
"pg-monitor": "^2.0.0",
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
"tsc-watch": "^6.0.4",
|
||||||
"tsconfig-link": "*",
|
"tsconfig-link": "*",
|
||||||
"typedoc": "^0.24.7",
|
"typedoc": "^0.24.7",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
|
|
@ -75,6 +78,7 @@
|
||||||
"serve:prod": "NODE_ENV=production npm run cli server",
|
"serve:prod": "NODE_ENV=production npm run cli server",
|
||||||
"worker": "NODE_ENV=development npm run cli worker",
|
"worker": "NODE_ENV=development npm run cli worker",
|
||||||
"worker:prod": "NODE_ENV=production npm run cli worker",
|
"worker:prod": "NODE_ENV=production npm run cli worker",
|
||||||
"watch:build": "tsc -p tsconfig.json -w"
|
"watch:build": "tsc -p tsconfig.json -w",
|
||||||
|
"dev": "tsc-watch --build --noClear --onSuccess \"node ./build/main/main.js\""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import type * as Hapi from "@hapi/hapi";
|
import type * as Hapi from "@hapi/hapi";
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
import type { IAppConfig } from "../config";
|
import type { IAppConfig } from "../config.js";
|
||||||
import * as Services from "./services";
|
import * as Services from "./services/index.js";
|
||||||
import * as Routes from "./routes";
|
import * as Routes from "./routes/index.js";
|
||||||
import * as Plugins from "./plugins";
|
import * as Plugins from "./plugins/index.js";
|
||||||
|
|
||||||
const AppPlugin = {
|
const AppPlugin = {
|
||||||
name: "App",
|
name: "App",
|
||||||
|
|
|
||||||
28
apps/metamigo-api/src/app/plugins/auth-bearer.ts
Normal file
28
apps/metamigo-api/src/app/plugins/auth-bearer.ts
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
import type * as Hapi from "@hapi/hapi";
|
||||||
|
import AuthBearer from "hapi-auth-bearer-token";
|
||||||
|
import { IAppConfig } from "@digiresilience/metamigo-config";
|
||||||
|
import { IMetamigoRepositories } from "@digiresilience/metamigo-common";
|
||||||
|
|
||||||
|
export const registerAuthBearer = async (
|
||||||
|
server: Hapi.Server,
|
||||||
|
config: IAppConfig
|
||||||
|
): Promise<void> => {
|
||||||
|
await server.register(AuthBearer);
|
||||||
|
|
||||||
|
server.auth.strategy("session-id-bearer-token", "bearer-access-token", {
|
||||||
|
allowQueryToken: false,
|
||||||
|
validate: async (
|
||||||
|
request: Hapi.Request,
|
||||||
|
token: string,
|
||||||
|
h: Hapi.ResponseToolkit
|
||||||
|
) => {
|
||||||
|
const repos = request.db() as IMetamigoRepositories;
|
||||||
|
const session = await repos.sessions.findBy({ sessionToken: token });
|
||||||
|
const isValid = !!session;
|
||||||
|
if (!isValid) return { isValid, credentials: {} };
|
||||||
|
const user = await repos.users.findById({ id: session.userId });
|
||||||
|
const credentials = { sessionToken: token, user };
|
||||||
|
return { isValid, credentials };
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -7,7 +7,8 @@ export const registerNextAuth = async (
|
||||||
server: Hapi.Server,
|
server: Hapi.Server,
|
||||||
config: IAppConfig
|
config: IAppConfig
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const nextAuthAdapterFactory: any = (request: Hapi.Request) => new NextAuthAdapter(request.db());
|
const nextAuthAdapterFactory: any = (request: Hapi.Request) =>
|
||||||
|
new NextAuthAdapter(request.db());
|
||||||
|
|
||||||
await server.register({
|
await server.register({
|
||||||
plugin: NextAuthPlugin,
|
plugin: NextAuthPlugin,
|
||||||
|
|
|
||||||
71
apps/metamigo-api/src/app/plugins/hapi-postgraphile.ts
Normal file
71
apps/metamigo-api/src/app/plugins/hapi-postgraphile.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import type * as Hapi from "@hapi/hapi";
|
||||||
|
import { IAppConfig } from "@digiresilience/metamigo-config";
|
||||||
|
import { postgraphile, HttpRequestHandler } from "postgraphile";
|
||||||
|
import { getPostGraphileOptions } from "@digiresilience/metamigo-db";
|
||||||
|
|
||||||
|
export interface HapiPostgraphileOptions {}
|
||||||
|
|
||||||
|
const PostgraphilePlugin: Hapi.Plugin<HapiPostgraphileOptions> = {
|
||||||
|
name: "postgraphilePlugin",
|
||||||
|
version: "1.0.0",
|
||||||
|
register: async function (server, options: HapiPostgraphileOptions) {
|
||||||
|
const config = server.config();
|
||||||
|
const postgraphileMiddleware: HttpRequestHandler = postgraphile(
|
||||||
|
config.postgraphile.authConnection,
|
||||||
|
"app_public",
|
||||||
|
{
|
||||||
|
...getPostGraphileOptions(),
|
||||||
|
jwtSecret: "",
|
||||||
|
pgSettings: async (req) => {
|
||||||
|
const auth = (req as any).hapiAuth;
|
||||||
|
if (auth.isAuthenticated && auth.credentials.user.userRole) {
|
||||||
|
return {
|
||||||
|
role: `app_${auth.credentials.user.userRole}`,
|
||||||
|
"jwt.claims.session_id": auth.credentials.sessionToken,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
role: "app_anonymous",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
server.route({
|
||||||
|
method: ["POST"],
|
||||||
|
path: "/graphql",
|
||||||
|
options: {
|
||||||
|
auth: "session-id-bearer-token",
|
||||||
|
payload: {
|
||||||
|
parse: false, // this disables payload parsing
|
||||||
|
output: "stream", // ensures the payload is a readable stream which postgraphile expects
|
||||||
|
},
|
||||||
|
},
|
||||||
|
handler: (request: Hapi.Request, h: Hapi.ResponseToolkit) => {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
const rawReq = request.raw.req as any;
|
||||||
|
rawReq.hapiAuth = request.auth;
|
||||||
|
postgraphileMiddleware(rawReq, request.raw.res, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
// PostGraphile responds directly to the request
|
||||||
|
resolve(h.abandon);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export const registerPostgraphile = async (
|
||||||
|
server: Hapi.Server,
|
||||||
|
config: IAppConfig
|
||||||
|
): Promise<void> => {
|
||||||
|
await server.register({
|
||||||
|
plugin: PostgraphilePlugin,
|
||||||
|
options: {},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
@ -5,12 +5,14 @@ import { makePlugin } from "@digiresilience/hapi-pg-promise";
|
||||||
|
|
||||||
import type { IAppConfig } from "../../config";
|
import type { IAppConfig } from "../../config";
|
||||||
import { dbInitOptions, IRepositories } from "@digiresilience/metamigo-db";
|
import { dbInitOptions, IRepositories } from "@digiresilience/metamigo-db";
|
||||||
import { registerNextAuth } from "./hapi-nextauth";
|
import { registerNextAuth } from "./hapi-nextauth.js";
|
||||||
import { registerSwagger } from "./swagger";
|
import { registerSwagger } from "./swagger.js";
|
||||||
import { registerNextAuthJwt } from "./nextauth-jwt";
|
import { registerCloudflareAccessJwt } from "./cloudflare-jwt.js";
|
||||||
import { registerCloudflareAccessJwt } from "./cloudflare-jwt";
|
import { registerAuthBearer } from "./auth-bearer.js";
|
||||||
import pg from "pg-promise/typescript/pg-subset";
|
import pg from "pg-promise/typescript/pg-subset";
|
||||||
|
|
||||||
|
import { registerPostgraphile } from "./hapi-postgraphile.js";
|
||||||
|
|
||||||
export const register = async (
|
export const register = async (
|
||||||
server: Hapi.Server,
|
server: Hapi.Server,
|
||||||
config: IAppConfig
|
config: IAppConfig
|
||||||
|
|
@ -34,6 +36,7 @@ export const register = async (
|
||||||
|
|
||||||
await registerNextAuth(server, config);
|
await registerNextAuth(server, config);
|
||||||
await registerSwagger(server);
|
await registerSwagger(server);
|
||||||
await registerNextAuthJwt(server, config);
|
|
||||||
await registerCloudflareAccessJwt(server, config);
|
await registerCloudflareAccessJwt(server, config);
|
||||||
|
await registerAuthBearer(server, config);
|
||||||
|
await registerPostgraphile(server, config);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,104 +0,0 @@
|
||||||
import * as Hoek from "@hapi/hoek";
|
|
||||||
import * as Hapi from "@hapi/hapi";
|
|
||||||
import type { IAppConfig } from "../../config";
|
|
||||||
|
|
||||||
// hapi-auth-jwt2 expects the key to be a raw key
|
|
||||||
const jwkToHapiAuthJwt2 = (jwkString) => {
|
|
||||||
try {
|
|
||||||
const jwk = JSON.parse(jwkString);
|
|
||||||
return Buffer.from(jwk.k, "base64");
|
|
||||||
} catch {
|
|
||||||
throw new Error(
|
|
||||||
"Failed to parse key for JWT verification. This is probably an application configuration error."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const jwtDefaults = {
|
|
||||||
jwkeysB64: undefined,
|
|
||||||
validate: undefined,
|
|
||||||
strategyName: "nextauth-jwt",
|
|
||||||
};
|
|
||||||
|
|
||||||
const jwtRegister = async (server: Hapi.Server, options): Promise<void> => {
|
|
||||||
server.dependency(["hapi-auth-jwt2"]);
|
|
||||||
const settings = Hoek.applyToDefaults(jwtDefaults, options);
|
|
||||||
const key = settings.jwkeysB64.map((k) => jwkToHapiAuthJwt2(k));
|
|
||||||
|
|
||||||
if (!settings.strategyName) {
|
|
||||||
throw new Error("Missing strategy name in nextauth-jwt pluginsettings!");
|
|
||||||
}
|
|
||||||
|
|
||||||
server.auth.strategy(settings.strategyName, "jwt", {
|
|
||||||
key,
|
|
||||||
cookieKey: false,
|
|
||||||
urlKey: false,
|
|
||||||
validate: settings.validate,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export const registerNextAuthJwt = async (
|
|
||||||
server: Hapi.Server,
|
|
||||||
config: IAppConfig
|
|
||||||
): Promise<void> => {
|
|
||||||
if (config.nextAuth.signingKey) {
|
|
||||||
await server.register({
|
|
||||||
plugin: {
|
|
||||||
name: "nextauth-jwt",
|
|
||||||
version: "0.0.2",
|
|
||||||
register: jwtRegister,
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
jwkeysB64: [config.nextAuth.signingKey],
|
|
||||||
async validate(decoded, request: Hapi.Request) {
|
|
||||||
const { email, name, role } = decoded;
|
|
||||||
const user = await request.db().users.findBy({ email });
|
|
||||||
if (!config.isProd) {
|
|
||||||
server.logger.info(
|
|
||||||
{
|
|
||||||
email,
|
|
||||||
name,
|
|
||||||
role,
|
|
||||||
},
|
|
||||||
"nextauth-jwt authorizing request"
|
|
||||||
);
|
|
||||||
// server.logger.info({ user }, "nextauth-jwt user result");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
isValid: Boolean(user && user.isActive),
|
|
||||||
// this credentials object is made available in every request
|
|
||||||
// at `request.auth.credentials`
|
|
||||||
credentials: { email, name, role },
|
|
||||||
};
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} else if (config.isProd) {
|
|
||||||
throw new Error("Missing nextauth.signingKey configuration value.");
|
|
||||||
} else {
|
|
||||||
server.log(
|
|
||||||
["warn"],
|
|
||||||
"Missing nextauth.signingKey configuration value. Authentication of nextauth endpoints disabled!"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// @hapi/jwt expects the key in its own format
|
|
||||||
/* UNUSED
|
|
||||||
const _jwkToHapiJwt = (jwkString) => {
|
|
||||||
try {
|
|
||||||
const jwk = JSON.parse(jwkString);
|
|
||||||
const rawKey = Buffer.from(jwk.k, "base64");
|
|
||||||
return {
|
|
||||||
key: rawKey,
|
|
||||||
algorithms: [jwk.alg],
|
|
||||||
kid: jwk.kid,
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
throw new Error(
|
|
||||||
"Failed to parse key for JWT verification. This is probably an application configuration error."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
@ -4,7 +4,7 @@ import Toys from "@hapipal/toys";
|
||||||
export const withDefaults = Toys.withRouteDefaults({
|
export const withDefaults = Toys.withRouteDefaults({
|
||||||
options: {
|
options: {
|
||||||
cors: true,
|
cors: true,
|
||||||
auth: "nextauth-jwt",
|
auth: "session-id-bearer-token",
|
||||||
validate: {
|
validate: {
|
||||||
failAction: Metamigo.validatingFailAction,
|
failAction: Metamigo.validatingFailAction,
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import isFunction from "lodash/isFunction";
|
import isFunction from "lodash/isFunction.js";
|
||||||
import type * as Hapi from "@hapi/hapi";
|
import type * as Hapi from "@hapi/hapi";
|
||||||
import * as UserRoutes from "./users";
|
import * as UserRoutes from "./users/index.js";
|
||||||
import * as VoiceRoutes from "./voice";
|
import * as VoiceRoutes from "./voice/index.js";
|
||||||
import * as WhatsappRoutes from "./whatsapp";
|
import * as WhatsappRoutes from "./whatsapp/index.js";
|
||||||
import * as SignalRoutes from "./signal";
|
import * as SignalRoutes from "./signal/index.js";
|
||||||
|
|
||||||
const loadRouteIndex = async (server, index) => {
|
const loadRouteIndex = async (server, index) => {
|
||||||
const routes = [];
|
const routes = [];
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
import * as Helpers from "../helpers";
|
import * as Helpers from "../helpers/index.js";
|
||||||
import Boom from "@hapi/boom";
|
import Boom from "@hapi/boom";
|
||||||
|
|
||||||
const getSignalService = (request) => request.services("app").signaldService;
|
const getSignalService = (request) => request.services("app").signaldService;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import {
|
import {
|
||||||
UserRecord,
|
UserRecord,
|
||||||
crudRoutesFor,
|
crudRoutesFor,
|
||||||
CrudControllerBase,
|
CrudControllerBase,
|
||||||
} from "@digiresilience/metamigo-common";
|
} from "@digiresilience/metamigo-common";
|
||||||
import * as RouteHelpers from "../helpers";
|
import * as RouteHelpers from "../helpers/index.js";
|
||||||
|
|
||||||
class UserRecordController extends CrudControllerBase(UserRecord) {}
|
class UserRecordController extends CrudControllerBase(UserRecord) {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
import * as Boom from "@hapi/boom";
|
import * as Boom from "@hapi/boom";
|
||||||
import * as R from "remeda";
|
import * as R from "remeda";
|
||||||
import * as Helpers from "../helpers";
|
import * as Helpers from "../helpers/index.js";
|
||||||
import Twilio from "twilio";
|
import Twilio from "twilio";
|
||||||
import {
|
import {
|
||||||
crudRoutesFor,
|
crudRoutesFor,
|
||||||
|
|
@ -66,7 +66,7 @@ export const VoiceProviderRoutes = Helpers.withDefaults([
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
class VoiceLineRecordController extends CrudControllerBase(VoiceLineRecord) { }
|
class VoiceLineRecordController extends CrudControllerBase(VoiceLineRecord) {}
|
||||||
|
|
||||||
const validator = (): Record<string, Hapi.RouteOptionsValidate> => ({
|
const validator = (): Record<string, Hapi.RouteOptionsValidate> => ({
|
||||||
create: {
|
create: {
|
||||||
|
|
@ -122,4 +122,4 @@ export const VoiceLineRoutes = Helpers.withDefaults(
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
export * from "./twilio";
|
export * from "./twilio/index.js";
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
import * as Boom from "@hapi/boom";
|
import * as Boom from "@hapi/boom";
|
||||||
import Twilio from "twilio";
|
import Twilio from "twilio";
|
||||||
import { SavedVoiceProvider } from "@digiresilience/metamigo-db";
|
import { SavedVoiceProvider } from "@digiresilience/metamigo-db";
|
||||||
import pMemoize from "p-memoize";
|
import pMemoize from "p-memoize";
|
||||||
import ExpiryMap from "expiry-map";
|
import ExpiryMap from "expiry-map";
|
||||||
import ms from "ms";
|
import ms from "ms";
|
||||||
import * as Helpers from "../../helpers";
|
import * as Helpers from "../../helpers/index.js";
|
||||||
import workerUtils from "../../../../worker-utils";
|
import workerUtils from "../../../../worker-utils.js";
|
||||||
|
|
||||||
const queueRecording = async (meta) =>
|
const queueRecording = async (meta) =>
|
||||||
workerUtils.addJob("twilio-recording", meta, { jobKey: meta.callSid });
|
workerUtils.addJob("twilio-recording", meta, { jobKey: meta.callSid });
|
||||||
|
|
@ -91,7 +91,7 @@ export const TwilioRoutes = Helpers.noAuth([
|
||||||
},
|
},
|
||||||
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
async handler(request: Hapi.Request, _h: Hapi.ResponseToolkit) {
|
||||||
const { voiceLineId } = request.params;
|
const { voiceLineId } = request.params;
|
||||||
const { To } = request.payload as { To: string; };
|
const { To } = request.payload as { To: string };
|
||||||
const voiceLine = await request.db().voiceLines.findBy({ number: To });
|
const voiceLine = await request.db().voiceLines.findBy({ number: To });
|
||||||
if (!voiceLine) return Boom.notFound();
|
if (!voiceLine) return Boom.notFound();
|
||||||
if (voiceLine.id !== voiceLineId) return Boom.badRequest();
|
if (voiceLine.id !== voiceLineId) return Boom.badRequest();
|
||||||
|
|
@ -193,7 +193,7 @@ export const TwilioRoutes = Helpers.noAuth([
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
async handler(request: Hapi.Request, h: Hapi.ResponseToolkit) {
|
||||||
const { providerId } = request.params as { providerId: string; };
|
const { providerId } = request.params as { providerId: string };
|
||||||
const provider: SavedVoiceProvider = await request
|
const provider: SavedVoiceProvider = await request
|
||||||
.db()
|
.db()
|
||||||
.voiceProviders.findById({ id: providerId });
|
.voiceProviders.findById({ id: providerId });
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import * as Helpers from "../helpers";
|
import * as Helpers from "../helpers/index.js";
|
||||||
import Boom from "@hapi/boom";
|
import Boom from "@hapi/boom";
|
||||||
|
|
||||||
export const GetAllWhatsappBotsRoute = Helpers.withDefaults({
|
export const GetAllWhatsappBotsRoute = Helpers.withDefaults({
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import type * as Hapi from "@hapi/hapi";
|
import type * as Hapi from "@hapi/hapi";
|
||||||
import SettingsService from "./settings";
|
import SettingsService from "./settings.js";
|
||||||
import WhatsappService from "./whatsapp";
|
import WhatsappService from "./whatsapp.js";
|
||||||
import SignaldService from "./signald";
|
import SignaldService from "./signald.js";
|
||||||
|
|
||||||
export const register = async (server: Hapi.Server): Promise<void> => {
|
export const register = async (server: Hapi.Server): Promise<void> => {
|
||||||
// register your services here
|
// register your services here
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import {
|
||||||
ClientMessageWrapperv1,
|
ClientMessageWrapperv1,
|
||||||
} from "@digiresilience/node-signald";
|
} from "@digiresilience/node-signald";
|
||||||
import { SavedSignalBot as Bot } from "@digiresilience/metamigo-db";
|
import { SavedSignalBot as Bot } from "@digiresilience/metamigo-db";
|
||||||
import workerUtils from "../../worker-utils";
|
import workerUtils from "../../worker-utils.js";
|
||||||
|
|
||||||
export default class SignaldService extends Service {
|
export default class SignaldService extends Service {
|
||||||
signald: SignaldAPI;
|
signald: SignaldAPI;
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import makeWASocket, {
|
||||||
useMultiFileAuthState,
|
useMultiFileAuthState,
|
||||||
} from "@adiwajshing/baileys";
|
} from "@adiwajshing/baileys";
|
||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import workerUtils from "../../worker-utils";
|
import workerUtils from "../../worker-utils.js";
|
||||||
|
|
||||||
export type AuthCompleteCallback = (error?: string) => void;
|
export type AuthCompleteCallback = (error?: string) => void;
|
||||||
|
|
||||||
|
|
|
||||||
2
apps/metamigo-api/src/index.ts
Normal file
2
apps/metamigo-api/src/index.ts
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export * from "./server/index.js";
|
||||||
|
export * from "./logger.js";
|
||||||
8
apps/metamigo-api/src/main.ts
Normal file
8
apps/metamigo-api/src/main.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import { startWithout } from "@digiresilience/montar";
|
||||||
|
import "./index.js";
|
||||||
|
|
||||||
|
async function runServer(): Promise<void> {
|
||||||
|
await startWithout(["worker"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
runServer();
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import * as Metamigo from "@digiresilience/metamigo-common";
|
import * as Metamigo from "@digiresilience/metamigo-common";
|
||||||
import { defState } from "@digiresilience/montar";
|
import { defState } from "@digiresilience/montar";
|
||||||
import Manifest from "./manifest";
|
import Manifest from "./manifest.js";
|
||||||
import config, { IAppConfig } from "../config";
|
import config, { IAppConfig } from "../config.js";
|
||||||
|
|
||||||
export const deployment = async (
|
export const deployment = async (
|
||||||
config: IAppConfig,
|
config: IAppConfig,
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,8 @@ import * as Glue from "@hapi/glue";
|
||||||
import * as Metamigo from "@digiresilience/metamigo-common";
|
import * as Metamigo from "@digiresilience/metamigo-common";
|
||||||
import * as Blipp from "blipp";
|
import * as Blipp from "blipp";
|
||||||
import HapiBasic from "@hapi/basic";
|
import HapiBasic from "@hapi/basic";
|
||||||
import HapiJwt from "hapi-auth-jwt2";
|
import AppPlugin from "../app/index.js";
|
||||||
import HapiPostgraphile from "hapi-postgraphile";
|
import type { IAppConfig } from "../config.js";
|
||||||
import { getPostGraphileOptions } from "@digiresilience/metamigo-db";
|
|
||||||
import AppPlugin from "../app";
|
|
||||||
import type { IAppConfig } from "../config";
|
|
||||||
|
|
||||||
const build = async (config: IAppConfig): Promise<Glue.Manifest> => {
|
const build = async (config: IAppConfig): Promise<Glue.Manifest> => {
|
||||||
const { port, address } = config.server;
|
const { port, address } = config.server;
|
||||||
|
|
@ -24,9 +21,6 @@ const build = async (config: IAppConfig): Promise<Glue.Manifest> => {
|
||||||
},
|
},
|
||||||
register: {
|
register: {
|
||||||
plugins: [
|
plugins: [
|
||||||
// jwt plugin, required for our jwt auth plugin
|
|
||||||
{ plugin: HapiJwt },
|
|
||||||
|
|
||||||
// Blipp prints the nicely formatted list of endpoints at app boot
|
// Blipp prints the nicely formatted list of endpoints at app boot
|
||||||
{ plugin: Blipp },
|
{ plugin: Blipp },
|
||||||
|
|
||||||
|
|
@ -43,30 +37,6 @@ const build = async (config: IAppConfig): Promise<Glue.Manifest> => {
|
||||||
config,
|
config,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// load Postgraphile
|
|
||||||
{
|
|
||||||
plugin: HapiPostgraphile,
|
|
||||||
options: {
|
|
||||||
route: {
|
|
||||||
path: "/graphql",
|
|
||||||
options: {
|
|
||||||
auth: {
|
|
||||||
strategies: ["nextauth-jwt"],
|
|
||||||
mode: "optional",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
pgConfig: config.postgraphile.authConnection,
|
|
||||||
schemaName: "app_public",
|
|
||||||
schemaOptions: {
|
|
||||||
...getPostGraphileOptions(),
|
|
||||||
jwtAudiences: [config.nextAuth.audience],
|
|
||||||
jwtSecret: "",
|
|
||||||
// unauthenticated users will hit the database with this role
|
|
||||||
pgDefaultRole: "app_anonymous",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import * as Worker from "graphile-worker";
|
import * as Worker from "graphile-worker";
|
||||||
import { defState } from "@digiresilience/montar";
|
import { defState } from "@digiresilience/montar";
|
||||||
import config from "./config";
|
import config from "./config.js";
|
||||||
|
|
||||||
const startWorkerUtils = async (): Promise<Worker.WorkerUtils> => {
|
const startWorkerUtils = async (): Promise<Worker.WorkerUtils> => {
|
||||||
const workerUtils = await Worker.makeWorkerUtils({
|
const workerUtils = await Worker.makeWorkerUtils({
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,18 @@
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": ["jest", "node", "long"],
|
"types": ["jest", "node", "long"],
|
||||||
"lib": ["es2020", "DOM"]
|
"lib": ["es2020", "DOM"],
|
||||||
|
"composite": true,
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/.*.ts"],
|
"include": ["src/**/*.ts", "src/**/.*.ts"],
|
||||||
"exclude": ["node_modules/**"]
|
"exclude": ["node_modules/**"],
|
||||||
|
"references": [
|
||||||
|
{"path": "../../packages/metamigo-common" },
|
||||||
|
{"path": "../../packages/metamigo-config" },
|
||||||
|
{"path": "../../packages/metamigo-db" },
|
||||||
|
{"path": "../../packages/hapi-nextauth" },
|
||||||
|
{"path": "../../packages/hapi-pg-promise" },
|
||||||
|
{"path": "../../packages/node-signald" },
|
||||||
|
{"path": "../../packages/montar" }
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
apps/metamigo-cli/.eslintrc.js
Normal file
12
apps/metamigo-cli/.eslintrc.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
require("eslint-config-link/patch/modern-module-resolution");
|
||||||
|
module.exports = {
|
||||||
|
extends: [
|
||||||
|
"eslint-config-link/profile/node",
|
||||||
|
"eslint-config-link/profile/typescript",
|
||||||
|
"eslint-config-link/profile/jest",
|
||||||
|
],
|
||||||
|
parserOptions: { tsconfigRootDir: __dirname },
|
||||||
|
rules: {
|
||||||
|
"new-cap": "off"
|
||||||
|
},
|
||||||
|
};
|
||||||
54
apps/metamigo-cli/Dockerfile
Normal file
54
apps/metamigo-cli/Dockerfile
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
FROM node:20 as base
|
||||||
|
|
||||||
|
FROM base AS builder
|
||||||
|
ARG APP_DIR=/opt/metamigo-cli
|
||||||
|
RUN mkdir -p ${APP_DIR}/
|
||||||
|
RUN npm i -g turbo
|
||||||
|
WORKDIR ${APP_DIR}
|
||||||
|
COPY . .
|
||||||
|
RUN turbo prune --scope=@digiresilience/metamigo-cli --docker
|
||||||
|
|
||||||
|
|
||||||
|
FROM base AS installer
|
||||||
|
ARG APP_DIR=/opt/metamigo-cli
|
||||||
|
WORKDIR ${APP_DIR}
|
||||||
|
COPY .gitignore .gitignore
|
||||||
|
COPY --from=builder ${APP_DIR}/out/json/ .
|
||||||
|
COPY --from=builder ${APP_DIR}/out/package-lock.json ./package-lock.json
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
|
COPY --from=builder ${APP_DIR}/out/full/ .
|
||||||
|
RUN npm i -g turbo
|
||||||
|
RUN turbo run build --filter=metamigo-cli
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
ARG APP_DIR=/opt/metamigo-cli
|
||||||
|
WORKDIR ${APP_DIR}/
|
||||||
|
ARG BUILD_DATE
|
||||||
|
ARG VERSION
|
||||||
|
LABEL maintainer="Darren Clarke <darren@redaranj.com>"
|
||||||
|
LABEL org.label-schema.build-date=$BUILD_DATE
|
||||||
|
LABEL org.label-schema.version=$VERSION
|
||||||
|
ENV APP_DIR ${APP_DIR}
|
||||||
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
|
apt-get install -y --no-install-recommends \
|
||||||
|
dumb-init
|
||||||
|
RUN mkdir -p ${APP_DIR}
|
||||||
|
RUN chown -R node ${APP_DIR}/
|
||||||
|
|
||||||
|
USER node
|
||||||
|
WORKDIR ${APP_DIR}
|
||||||
|
COPY --from=installer ${APP_DIR}/node_modules/ ./node_modules/
|
||||||
|
COPY --from=installer ${APP_DIR}/packages/ ./packages/
|
||||||
|
COPY --from=installer ${APP_DIR}/apps/metamigo-cli/ ./apps/metamigo-cli/
|
||||||
|
COPY --from=installer ${APP_DIR}/apps/metamigo-api/ ./apps/metamigo-api/
|
||||||
|
COPY --from=installer ${APP_DIR}/apps/metamigo-worker/ ./apps/metamigo-worker/
|
||||||
|
COPY --from=installer ${APP_DIR}/package.json ./package.json
|
||||||
|
USER root
|
||||||
|
WORKDIR ${APP_DIR}/apps/metamigo-cli/
|
||||||
|
RUN chmod +x docker-entrypoint.sh
|
||||||
|
USER node
|
||||||
|
EXPOSE 3000
|
||||||
|
ENV PORT 3000
|
||||||
|
ENV NODE_ENV production
|
||||||
|
ENTRYPOINT ["/opt/metamigo-cli/apps/metamigo-cli/docker-entrypoint.sh"]
|
||||||
3
apps/metamigo-cli/babel.config.json
Normal file
3
apps/metamigo-cli/babel.config.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"presets": ["babel-preset-link"]
|
||||||
|
}
|
||||||
4
apps/metamigo-cli/cli
Executable file
4
apps/metamigo-cli/cli
Executable file
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
|
||||||
|
node ./build/main/index.js ${@}
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
#!/bin/bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
cd ${AMIGO_DIR}
|
|
||||||
|
|
||||||
if [[ "$1" == "api" ]]; then
|
if [[ "$1" == "api" ]]; then
|
||||||
echo "docker-entrypoint: starting api server"
|
echo "docker-entrypoint: starting api server"
|
||||||
./cli db -- migrate
|
./cli db -- migrate
|
||||||
|
|
@ -10,9 +8,6 @@ if [[ "$1" == "api" ]]; then
|
||||||
elif [[ "$1" == "worker" ]]; then
|
elif [[ "$1" == "worker" ]]; then
|
||||||
echo "docker-entrypoint: starting worker"
|
echo "docker-entrypoint: starting worker"
|
||||||
exec dumb-init ./cli worker
|
exec dumb-init ./cli worker
|
||||||
elif [[ "$1" == "frontend" ]]; then
|
|
||||||
echo "docker-entrypoint: starting frontend"
|
|
||||||
exec dumb-init yarn workspace @app/frontend start
|
|
||||||
elif [[ "$1" == "cli" ]]; then
|
elif [[ "$1" == "cli" ]]; then
|
||||||
echo "docker-entrypoint: starting frontend"
|
echo "docker-entrypoint: starting frontend"
|
||||||
shift 1
|
shift 1
|
||||||
4
apps/metamigo-cli/jest.config.json
Normal file
4
apps/metamigo-cli/jest.config.json
Normal file
|
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"preset": "jest-config-link",
|
||||||
|
"setupFiles": ["<rootDir>/src/setup.test.ts"]
|
||||||
|
}
|
||||||
|
|
@ -4,18 +4,26 @@
|
||||||
"main": "build/main/index.js",
|
"main": "build/main/index.js",
|
||||||
"author": "Abel Luck <abel@guardianproject.info>",
|
"author": "Abel Luck <abel@guardianproject.info>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"metamigo": "./build/main/index.js"
|
||||||
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@digiresilience/montar": "*",
|
"@digiresilience/montar": "*",
|
||||||
"@digiresilience/metamigo-config": "*",
|
"@digiresilience/metamigo-config": "*",
|
||||||
|
"@digiresilience/metamigo-common": "*",
|
||||||
"@digiresilience/metamigo-db": "*",
|
"@digiresilience/metamigo-db": "*",
|
||||||
|
"@digiresilience/metamigo-api": "*",
|
||||||
|
"@digiresilience/metamigo-worker": "*",
|
||||||
"commander": "^10.0.1",
|
"commander": "^10.0.1",
|
||||||
"graphile-migrate": "^1.4.1",
|
"graphile-migrate": "^1.4.1",
|
||||||
"graphile-worker": "^0.13.0",
|
"graphile-worker": "^0.13.0",
|
||||||
"node-jose": "^2.2.0",
|
"node-jose": "^2.2.0",
|
||||||
"postgraphile": "4.13.0",
|
"postgraphile": "4.12.3",
|
||||||
"graphql": "16.6.0"
|
"graphql": "15.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/jest": "^29.5.1",
|
||||||
"pino-pretty": "^10.0.0",
|
"pino-pretty": "^10.0.0",
|
||||||
"nodemon": "^2.0.22",
|
"nodemon": "^2.0.22",
|
||||||
"tsconfig-link": "*",
|
"tsconfig-link": "*",
|
||||||
|
|
@ -25,11 +33,11 @@
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"migrate": "NODE_ENV=development node --unhandled-rejections=strict build/main/index.js db -- migrate",
|
||||||
"build": "tsc -p tsconfig.json",
|
"build": "tsc -p tsconfig.json",
|
||||||
"cli": "NODE_ENV=development node --unhandled-rejections=strict build/main/index.js",
|
|
||||||
"fix:lint": "eslint src --ext .ts --fix",
|
"fix:lint": "eslint src --ext .ts --fix",
|
||||||
"fmt": "prettier \"src/**/*.ts\" --write",
|
"fmt": "prettier \"src/**/*.ts\" --write",
|
||||||
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
||||||
"test": "echo no tests"
|
"test": "echo no tests"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,11 +2,12 @@ import {
|
||||||
generateConfig,
|
generateConfig,
|
||||||
printConfigOptions,
|
printConfigOptions,
|
||||||
} from "@digiresilience/metamigo-common";
|
} from "@digiresilience/metamigo-common";
|
||||||
|
import { IAppConfig, IAppConvict } from "@digiresilience/metamigo-config";
|
||||||
import { loadConfigRaw } from "@digiresilience/metamigo-config";
|
import { loadConfigRaw } from "@digiresilience/metamigo-config";
|
||||||
|
|
||||||
export const genConf = async (): Promise<void> => {
|
export const genConf = async (): Promise<void> => {
|
||||||
const c = await loadConfigRaw();
|
const c = (await loadConfigRaw()) as any;
|
||||||
const generated = generateConfig(c);
|
const generated = generateConfig(c) as any;
|
||||||
console.log(generated);
|
console.log(generated);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -16,6 +17,6 @@ export const genSchema = async (): Promise<void> => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const listConfig = async (): Promise<void> => {
|
export const listConfig = async (): Promise<void> => {
|
||||||
const c = await loadConfigRaw();
|
const c = (await loadConfigRaw()) as any;
|
||||||
printConfigOptions(c);
|
printConfigOptions(c);
|
||||||
};
|
};
|
||||||
|
|
@ -4,12 +4,11 @@ import { Command } from "commander";
|
||||||
import { startWithout } from "@digiresilience/montar";
|
import { startWithout } from "@digiresilience/montar";
|
||||||
import { migrateWrapper } from "@digiresilience/metamigo-db";
|
import { migrateWrapper } from "@digiresilience/metamigo-db";
|
||||||
import { loadConfig } from "@digiresilience/metamigo-config";
|
import { loadConfig } from "@digiresilience/metamigo-config";
|
||||||
import { genConf, listConfig } from "./config";
|
import { genConf, listConfig } from "./config.js";
|
||||||
import { createTokenForTesting, generateJwks } from "./jwks";
|
import { createTokenForTesting, generateJwks } from "./jwks.js";
|
||||||
import { exportGraphqlSchema } from "./metamigo-postgraphile";
|
import { exportGraphqlSchema } from "./metamigo-postgraphile.js";
|
||||||
import "api/build/main/server";
|
import "@digiresilience/metamigo-api";
|
||||||
import "api/build/main/logger";
|
import "@digiresilience/metamigo-worker";
|
||||||
import "worker/build/main";
|
|
||||||
|
|
||||||
const program = new Command();
|
const program = new Command();
|
||||||
|
|
||||||
|
|
@ -7,10 +7,12 @@ import {
|
||||||
printSchema,
|
printSchema,
|
||||||
} from "graphql";
|
} from "graphql";
|
||||||
import { createPostGraphileSchema } from "postgraphile";
|
import { createPostGraphileSchema } from "postgraphile";
|
||||||
import { Pool } from "pg";
|
import pg from "pg";
|
||||||
import { loadConfig } from "@digiresilience/metamigo-config";
|
import { loadConfig } from "@digiresilience/metamigo-config";
|
||||||
import { getPostGraphileOptions } from "@digiresilience/metamigo-db";
|
import { getPostGraphileOptions } from "@digiresilience/metamigo-db";
|
||||||
|
|
||||||
|
const { Pool } = pg;
|
||||||
|
|
||||||
export const exportGraphqlSchema = async (): Promise<void> => {
|
export const exportGraphqlSchema = async (): Promise<void> => {
|
||||||
const config = await loadConfig();
|
const config = await loadConfig();
|
||||||
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
{
|
{
|
||||||
"extends": "tsconfig-link",
|
"extends": "tsconfig-link",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["es2020", "DOM"],
|
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"outDir": "build/main",
|
"outDir": "build/main",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"baseUrl": "./",
|
"baseUrl": "./",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"types": ["jest", "node"]
|
"types": ["jest", "node"],
|
||||||
|
"esModuleInterop": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts"],
|
"include": ["src/**/*.ts"],
|
||||||
"exclude": ["node_modules/**"]
|
"exclude": ["node_modules/**"]
|
||||||
|
|
@ -1,61 +1,52 @@
|
||||||
FROM node:20-bullseye as builder
|
FROM node:20 as base
|
||||||
|
|
||||||
ARG METAMIGO_DIR=/opt/metamigo
|
FROM base AS builder
|
||||||
RUN mkdir -p ${METAMIGO_DIR}/
|
ARG APP_DIR=/opt/metamigo-frontend
|
||||||
WORKDIR ${METAMIGO_DIR}
|
RUN mkdir -p ${APP_DIR}/
|
||||||
COPY package.json tsconfig.json ${METAMIGO_DIR}/
|
RUN npm i -g turbo
|
||||||
COPY . ${METAMIGO_DIR}/
|
WORKDIR ${APP_DIR}
|
||||||
|
COPY . .
|
||||||
|
RUN turbo prune --scope=@digiresilience/metamigo-frontend --docker
|
||||||
|
|
||||||
RUN npm --no-install tsc --build --verbose
|
|
||||||
RUN npm install
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
RUN rm -Rf ./node_modules
|
FROM base AS installer
|
||||||
|
ARG APP_DIR=/opt/metamigo-frontend
|
||||||
|
WORKDIR ${APP_DIR}
|
||||||
|
COPY .gitignore .gitignore
|
||||||
|
COPY --from=builder ${APP_DIR}/out/json/ .
|
||||||
|
COPY --from=builder ${APP_DIR}/out/package-lock.json ./package-lock.json
|
||||||
|
RUN npm ci --omit=dev
|
||||||
|
|
||||||
FROM node:20-bullseye as clean
|
COPY --from=builder ${APP_DIR}/out/full/ .
|
||||||
ARG METAMIGO_DIR=/opt/metamigo
|
RUN npm i -g turbo
|
||||||
|
RUN turbo run build --filter=metamigo-frontend
|
||||||
COPY --from=builder ${METAMIGO_DIR} ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
RUN rm -Rf ./node_modules
|
|
||||||
|
|
||||||
FROM node:20-bullseye as pristine
|
|
||||||
LABEL maintainer="Abel Luck <abel@guardianproject.info>"
|
|
||||||
|
|
||||||
|
FROM base AS runner
|
||||||
|
ARG APP_DIR=/opt/metamigo-frontend
|
||||||
|
WORKDIR ${APP_DIR}/
|
||||||
|
ARG BUILD_DATE
|
||||||
|
ARG VERSION
|
||||||
|
LABEL maintainer="Darren Clarke <darren@redaranj.com>"
|
||||||
|
LABEL org.label-schema.build-date=$BUILD_DATE
|
||||||
|
LABEL org.label-schema.version=$VERSION
|
||||||
|
ENV APP_DIR ${APP_DIR}
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
||||||
apt-get install -y --no-install-recommends --fix-missing \
|
apt-get install -y --no-install-recommends \
|
||||||
postgresql-client dumb-init ffmpeg
|
dumb-init
|
||||||
|
RUN mkdir -p ${APP_DIR}
|
||||||
ARG METAMIGO_DIR=/opt/metamigo
|
RUN chown -R node ${APP_DIR}/
|
||||||
ENV METAMIGO_DIR ${METAMIGO_DIR}
|
|
||||||
RUN mkdir -p ${METAMIGO_DIR}
|
|
||||||
RUN chown -R node:node ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
|
||||||
RUN chmod +x /docker-entrypoint.sh
|
|
||||||
|
|
||||||
COPY --from=clean ${METAMIGO_DIR}/ ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
WORKDIR ${METAMIGO_DIR}
|
|
||||||
|
|
||||||
USER node
|
USER node
|
||||||
|
WORKDIR ${APP_DIR}
|
||||||
|
COPY --from=installer ${APP_DIR}/node_modules/ ./node_modules/
|
||||||
|
COPY --from=installer ${APP_DIR}/packages/ ./packages/
|
||||||
|
COPY --from=installer ${APP_DIR}/apps/metamigo-frontend/ ./apps/metamigo-frontend/
|
||||||
|
COPY --from=installer ${APP_DIR}/package.json ./package.json
|
||||||
|
USER root
|
||||||
|
WORKDIR ${APP_DIR}/apps/metamigo-frontend/
|
||||||
|
RUN chmod +x docker-entrypoint.sh
|
||||||
|
USER node
|
||||||
EXPOSE 3000
|
EXPOSE 3000
|
||||||
EXPOSE 3001
|
|
||||||
EXPOSE 3002
|
|
||||||
ENV PORT 3000
|
ENV PORT 3000
|
||||||
ENV NODE_ENV production
|
ENV NODE_ENV production
|
||||||
|
ENTRYPOINT ["/opt/metamigo-frontend/apps/metamigo-frontend/docker-entrypoint.sh"]
|
||||||
ARG BUILD_DATE
|
|
||||||
ARG VCS_REF
|
|
||||||
ARG VCS_URL="https://gitlab.com/digiresilience/link/metamigo"
|
|
||||||
ARG VERSION
|
|
||||||
LABEL org.label-schema.schema-version="1.0"
|
|
||||||
LABEL org.label-schema.name="digiresilience.org/link/metamigo"
|
|
||||||
LABEL org.label-schema.description="part of CDR Link"
|
|
||||||
LABEL org.label-schema.build-date=$BUILD_DATE
|
|
||||||
LABEL org.label-schema.vcs-url=$VCS_URL
|
|
||||||
LABEL org.label-schema.vcs-ref=$VCS_REF
|
|
||||||
LABEL org.label-schema.version=$VERSION
|
|
||||||
|
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
export {default as AppBar} from "./AppBar";
|
export { default as AppBar } from "./AppBar";
|
||||||
export {default as Layout} from "./Layout";
|
export { default as Layout } from "./Layout";
|
||||||
export {default as Menu} from "./Menu";
|
export { default as Menu } from "./Menu";
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,9 @@ export const theme = {
|
||||||
background: {
|
background: {
|
||||||
default: "#fff",
|
default: "#fff",
|
||||||
},
|
},
|
||||||
getContrastText(color: string) { return color === "#ffffff" ? "#000" : "#fff"; },
|
getContrastText(color: string) {
|
||||||
|
return color === "#ffffff" ? "#000" : "#fff";
|
||||||
|
},
|
||||||
},
|
},
|
||||||
shape: {
|
shape: {
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
|
|
|
||||||
|
|
@ -138,7 +138,7 @@ const handleRequestCode = async ({
|
||||||
verifyMode,
|
verifyMode,
|
||||||
id,
|
id,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onFailure,
|
onError,
|
||||||
captchaCode = undefined,
|
captchaCode = undefined,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
if (verifyMode === MODE.SMS) console.log("REQUESTING sms");
|
if (verifyMode === MODE.SMS) console.log("REQUESTING sms");
|
||||||
|
|
@ -160,7 +160,7 @@ const handleRequestCode = async ({
|
||||||
if (response && response.ok) {
|
if (response && response.ok) {
|
||||||
onSuccess();
|
onSuccess();
|
||||||
} else {
|
} else {
|
||||||
onFailure(response.status || 400);
|
onError(response.status || 400);
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
console.error("Failed to request verification code:", error);
|
console.error("Failed to request verification code:", error);
|
||||||
|
|
@ -171,7 +171,7 @@ const VerificationCodeRequest = ({
|
||||||
verifyMode,
|
verifyMode,
|
||||||
data,
|
data,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onFailure,
|
onError,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
|
|
@ -179,10 +179,10 @@ const VerificationCodeRequest = ({
|
||||||
verifyMode,
|
verifyMode,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onFailure,
|
onError,
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
}, [data.id, onFailure, onSuccess, verifyMode]);
|
}, [data.id, onError, onSuccess, verifyMode]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|
@ -204,7 +204,7 @@ const VerificationCaptcha = ({
|
||||||
verifyMode,
|
verifyMode,
|
||||||
data,
|
data,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onFailure,
|
onError,
|
||||||
handleClose,
|
handleClose,
|
||||||
}: any) => {
|
}: any) => {
|
||||||
const [code, setCode] = React.useState(undefined);
|
const [code, setCode] = React.useState(undefined);
|
||||||
|
|
@ -216,7 +216,7 @@ const VerificationCaptcha = ({
|
||||||
verifyMode,
|
verifyMode,
|
||||||
id: data.id,
|
id: data.id,
|
||||||
onSuccess,
|
onSuccess,
|
||||||
onFailure,
|
onError,
|
||||||
captchaCode: code,
|
captchaCode: code,
|
||||||
});
|
});
|
||||||
setSubmitting(false);
|
setSubmitting(false);
|
||||||
|
|
@ -367,7 +367,7 @@ const VerificationCodeDialog = (props: any) => {
|
||||||
props.handleClose();
|
props.handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
const onFailure = (code: number) => {
|
const onError = (code: number) => {
|
||||||
if (code === 402 || code === 500) {
|
if (code === 402 || code === 500) {
|
||||||
setStage("captcha");
|
setStage("captcha");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -385,7 +385,7 @@ const VerificationCodeDialog = (props: any) => {
|
||||||
<VerificationCodeRequest
|
<VerificationCodeRequest
|
||||||
mode={props.verifyMode}
|
mode={props.verifyMode}
|
||||||
onSuccess={onRequestSuccess}
|
onSuccess={onRequestSuccess}
|
||||||
onFailure={onFailure}
|
onError={onError}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
@ -400,7 +400,7 @@ const VerificationCodeDialog = (props: any) => {
|
||||||
<VerificationCaptcha
|
<VerificationCaptcha
|
||||||
mode={props.verifyMode}
|
mode={props.verifyMode}
|
||||||
onSuccess={onRequestSuccess}
|
onSuccess={onRequestSuccess}
|
||||||
onFailure={onRestartVerification}
|
onError={onRestartVerification}
|
||||||
handleClose={handleClose}
|
handleClose={handleClose}
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,10 @@ import {
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { UserRoleInput } from "./shared";
|
import { UserRoleInput } from "./shared";
|
||||||
|
|
||||||
const UserCreate: FC<CreateProps> = (props: any) => {
|
const UserCreate: FC<CreateProps> = () => {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
return (
|
return (
|
||||||
<Create {...props} title="Create Users">
|
<Create title="Create Users">
|
||||||
<SimpleForm>
|
<SimpleForm>
|
||||||
<TextInput source="email" />
|
<TextInput source="email" />
|
||||||
<TextInput source="name" />
|
<TextInput source="name" />
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,8 @@ import {
|
||||||
Toolbar,
|
Toolbar,
|
||||||
SaveButton,
|
SaveButton,
|
||||||
DeleteButton,
|
DeleteButton,
|
||||||
EditProps,
|
|
||||||
useRedirect,
|
useRedirect,
|
||||||
|
useRecordContext,
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
import { UserRoleInput } from "./shared";
|
import { UserRoleInput } from "./shared";
|
||||||
|
|
@ -23,16 +23,20 @@ const useStyles = makeStyles((_theme: any) => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const UserEditToolbar = (props: any) => {
|
const UserEditToolbar = (props: any) => {
|
||||||
const classes = useStyles(props);
|
const classes = useStyles();
|
||||||
const redirect = useRedirect();
|
const redirect = useRedirect();
|
||||||
|
const record = useRecordContext();
|
||||||
|
const {session} = props;
|
||||||
|
|
||||||
|
const shouldDisableDelete = !session || !session.user || session.user.id === record.id;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Toolbar className={classes.defaultToolbar} {...props}>
|
<Toolbar className={classes.defaultToolbar}>
|
||||||
<SaveButton
|
<SaveButton
|
||||||
label="save"
|
label="save"
|
||||||
mutationOptions={{ onSuccess: () => redirect("/users") }}
|
mutationOptions={{ onSuccess: () => redirect("/users") }}
|
||||||
/>
|
/>
|
||||||
<DeleteButton disabled={props.session.user.id === props.record.id} />
|
<DeleteButton disabled={shouldDisableDelete} />
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
@ -43,11 +47,11 @@ const UserTitle = ({ record }: { record?: any }) => {
|
||||||
return <span>User {title}</span>;
|
return <span>User {title}</span>;
|
||||||
};
|
};
|
||||||
|
|
||||||
const UserEdit = (props: EditProps) => {
|
const UserEdit = () => {
|
||||||
const { data: session } = useSession();
|
const { data: session } = useSession();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Edit title={<UserTitle />} {...props}>
|
<Edit title={<UserTitle />}>
|
||||||
<SimpleForm toolbar={<UserEditToolbar session={session} />}>
|
<SimpleForm toolbar={<UserEditToolbar session={session} />}>
|
||||||
<TextInput disabled source="id" />
|
<TextInput disabled source="id" />
|
||||||
<TextInput source="email" />
|
<TextInput source="email" />
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,10 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
EmailField,
|
EmailField,
|
||||||
BooleanField,
|
BooleanField,
|
||||||
ListProps,
|
|
||||||
} from "react-admin";
|
} from "react-admin";
|
||||||
|
|
||||||
const UserList = (props: ListProps) => (
|
const UserList = () => (
|
||||||
<List {...props} exporter={false}>
|
<List exporter={false}>
|
||||||
<Datagrid rowClick="edit">
|
<Datagrid rowClick="edit">
|
||||||
<EmailField source="email" />
|
<EmailField source="email" />
|
||||||
<DateField source="emailVerified" />
|
<DateField source="emailVerified" />
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
import { SelectInput } from "react-admin";
|
import { SelectInput, useRecordContext } from "react-admin";
|
||||||
|
|
||||||
export const UserRoleInput = (props: any) => (
|
export const UserRoleInput = (props: any) => {
|
||||||
<SelectInput
|
const record = useRecordContext();
|
||||||
source="userRole"
|
return (
|
||||||
choices={[
|
<SelectInput
|
||||||
{ id: "NONE", name: "None" },
|
source="userRole"
|
||||||
{ id: "USER", name: "User" },
|
choices={[
|
||||||
{ id: "ADMIN", name: "Admin" },
|
{ id: "NONE", name: "None" },
|
||||||
]}
|
{ id: "USER", name: "User" },
|
||||||
disabled={props.session.user.id === props.record.id}
|
{ id: "ADMIN", name: "Admin" },
|
||||||
{...props}
|
]}
|
||||||
/>
|
disabled={props.session.user.id === record.id}
|
||||||
);
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -117,7 +117,7 @@ const Sidebar = ({ record }: any) => {
|
||||||
|
|
||||||
const WhatsappBotShow = (props: ShowProps) => {
|
const WhatsappBotShow = (props: ShowProps) => {
|
||||||
const refresh = useRefresh();
|
const refresh = useRefresh();
|
||||||
const { data } = useGetOne("whatsappBots", props.id as any);
|
const { data } = useGetOne("whatsappBots", {id: props.id});
|
||||||
|
|
||||||
const { data: registerData, error: registerError } = useSWR(
|
const { data: registerData, error: registerError } = useSWR(
|
||||||
data && !data?.isVerified
|
data && !data?.isVerified
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,5 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
set -e
|
set -e
|
||||||
|
echo "starting leafcutter"
|
||||||
cd ${AMIGO_DIR}
|
exec dumb-init npm run start
|
||||||
|
|
||||||
if [[ "$1" == "api" ]]; then
|
|
||||||
echo "docker-entrypoint: starting api server"
|
|
||||||
./cli db -- migrate
|
|
||||||
exec dumb-init ./cli api
|
|
||||||
elif [[ "$1" == "worker" ]]; then
|
|
||||||
echo "docker-entrypoint: starting worker"
|
|
||||||
exec dumb-init ./cli worker
|
|
||||||
elif [[ "$1" == "frontend" ]]; then
|
|
||||||
echo "docker-entrypoint: starting frontend"
|
|
||||||
exec dumb-init yarn workspace @app/frontend start
|
|
||||||
elif [[ "$1" == "cli" ]]; then
|
|
||||||
echo "docker-entrypoint: starting frontend"
|
|
||||||
shift 1
|
|
||||||
exec ./cli "$@"
|
|
||||||
else
|
|
||||||
echo "docker-entrypoint: missing argument, one of: api, worker, frontend, cli"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@ const customEnglishMessages: TranslationMessages = {
|
||||||
signalBots: {
|
signalBots: {
|
||||||
name: "Signal Bot |||| Signal Bots",
|
name: "Signal Bot |||| Signal Bots",
|
||||||
verifyDialog: {
|
verifyDialog: {
|
||||||
sms:
|
sms: "Please enter the verification code sent via SMS to %{phoneNumber}",
|
||||||
"Please enter the verification code sent via SMS to %{phoneNumber}",
|
|
||||||
voice:
|
voice:
|
||||||
"Please answer the call from Signal to %{phoneNumber} and enter the verification code",
|
"Please answer the call from Signal to %{phoneNumber} and enter the verification code",
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -100,13 +100,15 @@ export const getIdentity = async (
|
||||||
|
|
||||||
const cloudflareAccountProvider = "cloudflare-access";
|
const cloudflareAccountProvider = "cloudflare-access";
|
||||||
|
|
||||||
const cloudflareAuthorizeCallback = (
|
const cloudflareAuthorizeCallback =
|
||||||
req: IncomingMessage,
|
(
|
||||||
domain: string,
|
req: IncomingMessage,
|
||||||
verifier: VerifyFn,
|
domain: string,
|
||||||
adapter: Adapter
|
verifier: VerifyFn,
|
||||||
): (() => Promise<any>) => async () => {
|
adapter: Adapter
|
||||||
/*
|
): (() => Promise<any>) =>
|
||||||
|
async () => {
|
||||||
|
/*
|
||||||
|
|
||||||
lots of little variables in here.
|
lots of little variables in here.
|
||||||
|
|
||||||
|
|
@ -118,75 +120,75 @@ const cloudflareAuthorizeCallback = (
|
||||||
profile: this is the accumulated user information we have that we will fetch/build the user record with
|
profile: this is the accumulated user information we have that we will fetch/build the user record with
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const { token, decoded } = await verifyRequest(verifier, req);
|
const { token, decoded } = await verifyRequest(verifier, req);
|
||||||
|
|
||||||
const profile = {
|
const profile = {
|
||||||
email: undefined,
|
email: undefined,
|
||||||
name: undefined,
|
name: undefined,
|
||||||
avatar: undefined,
|
avatar: undefined,
|
||||||
|
};
|
||||||
|
if (decoded.email) profile.email = decoded.email;
|
||||||
|
if (decoded.name) profile.name = decoded.name;
|
||||||
|
const identity = await getIdentity(domain, token);
|
||||||
|
|
||||||
|
if (identity.email) profile.email = identity.email;
|
||||||
|
if (identity.name) profile.name = identity.name;
|
||||||
|
|
||||||
|
if (!profile.email)
|
||||||
|
throw new Error("cloudflare access authorization: email not found");
|
||||||
|
|
||||||
|
const providerId = `cfaccess|${identity.idp.type}|${identity.idp.id}`;
|
||||||
|
const providerAccountId = identity.user_uuid;
|
||||||
|
|
||||||
|
if (!providerAccountId)
|
||||||
|
throw new Error(
|
||||||
|
"cloudflare access authorization: missing provider account id"
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
getUserByProviderAccountId,
|
||||||
|
getUserByEmail,
|
||||||
|
createUser,
|
||||||
|
linkAccount,
|
||||||
|
} =
|
||||||
|
// @ts-expect-error: non-existent property
|
||||||
|
await adapter.getAdapter({} as any);
|
||||||
|
|
||||||
|
const userByProviderAccountId = await getUserByProviderAccountId(
|
||||||
|
providerId,
|
||||||
|
providerAccountId
|
||||||
|
);
|
||||||
|
if (userByProviderAccountId) {
|
||||||
|
return userByProviderAccountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
const userByEmail = await getUserByEmail(profile.email);
|
||||||
|
if (userByEmail) {
|
||||||
|
// we will not explicitly link accounts
|
||||||
|
throw new Error(
|
||||||
|
"cloudflare access authorization: user exists for email address, but is not linked."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await createUser(profile);
|
||||||
|
|
||||||
|
// between the previous line and the next line exists a transactional bug
|
||||||
|
// https://github.com/nextauthjs/next-auth/issues/876
|
||||||
|
// hopefully we don't experience it
|
||||||
|
|
||||||
|
await linkAccount(
|
||||||
|
user.id,
|
||||||
|
providerId,
|
||||||
|
cloudflareAccountProvider,
|
||||||
|
providerAccountId,
|
||||||
|
// the following are unused but are specified for completness
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
return user;
|
||||||
};
|
};
|
||||||
if (decoded.email) profile.email = decoded.email;
|
|
||||||
if (decoded.name) profile.name = decoded.name;
|
|
||||||
const identity = await getIdentity(domain, token);
|
|
||||||
|
|
||||||
if (identity.email) profile.email = identity.email;
|
|
||||||
if (identity.name) profile.name = identity.name;
|
|
||||||
|
|
||||||
if (!profile.email)
|
|
||||||
throw new Error("cloudflare access authorization: email not found");
|
|
||||||
|
|
||||||
const providerId = `cfaccess|${identity.idp.type}|${identity.idp.id}`;
|
|
||||||
const providerAccountId = identity.user_uuid;
|
|
||||||
|
|
||||||
if (!providerAccountId)
|
|
||||||
throw new Error(
|
|
||||||
"cloudflare access authorization: missing provider account id"
|
|
||||||
);
|
|
||||||
|
|
||||||
const {
|
|
||||||
getUserByProviderAccountId,
|
|
||||||
getUserByEmail,
|
|
||||||
createUser,
|
|
||||||
linkAccount,
|
|
||||||
} =
|
|
||||||
// @ts-expect-error: non-existent property
|
|
||||||
await adapter.getAdapter({} as any);
|
|
||||||
|
|
||||||
const userByProviderAccountId = await getUserByProviderAccountId(
|
|
||||||
providerId,
|
|
||||||
providerAccountId
|
|
||||||
);
|
|
||||||
if (userByProviderAccountId) {
|
|
||||||
return userByProviderAccountId;
|
|
||||||
}
|
|
||||||
|
|
||||||
const userByEmail = await getUserByEmail(profile.email);
|
|
||||||
if (userByEmail) {
|
|
||||||
// we will not explicitly link accounts
|
|
||||||
throw new Error(
|
|
||||||
"cloudflare access authorization: user exists for email address, but is not linked."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await createUser(profile);
|
|
||||||
|
|
||||||
// between the previous line and the next line exists a transactional bug
|
|
||||||
// https://github.com/nextauthjs/next-auth/issues/876
|
|
||||||
// hopefully we don't experience it
|
|
||||||
|
|
||||||
await linkAccount(
|
|
||||||
user.id,
|
|
||||||
providerId,
|
|
||||||
cloudflareAccountProvider,
|
|
||||||
providerAccountId,
|
|
||||||
// the following are unused but are specified for completness
|
|
||||||
undefined,
|
|
||||||
undefined,
|
|
||||||
undefined
|
|
||||||
);
|
|
||||||
|
|
||||||
return user;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param audience the cloudflare access audience id
|
* @param audience the cloudflare access audience id
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,5 @@ export const metamigoDataProvider = async (client: any) => {
|
||||||
{},
|
{},
|
||||||
{ introspection: { schema: schema.data.__schema } }
|
{ introspection: { schema: schema.data.__schema } }
|
||||||
);
|
);
|
||||||
|
return graphqlDataProvider;
|
||||||
const dataProvider = async (type: any, resource: any, params: any) => graphqlDataProvider(type, resource, params);
|
|
||||||
|
|
||||||
return dataProvider;
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
/* eslint-disable unicorn/no-null */
|
/* eslint-disable unicorn/no-null */
|
||||||
/* eslint-disable max-params */
|
import type {
|
||||||
import type { Adapter } from "next-auth/adapters";
|
Adapter,
|
||||||
// @ts-expect-error: Missing export
|
AdapterAccount,
|
||||||
import type { AppOptions } from "next-auth";
|
AdapterSession,
|
||||||
|
AdapterUser,
|
||||||
|
} from "next-auth/adapters";
|
||||||
import * as Wreck from "@hapi/wreck";
|
import * as Wreck from "@hapi/wreck";
|
||||||
import * as Boom from "@hapi/boom";
|
import * as Boom from "@hapi/boom";
|
||||||
|
|
||||||
|
|
@ -18,7 +20,7 @@ export interface Profile {
|
||||||
createdBy: string;
|
createdBy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type User = Profile & { id: string; createdAt: Date; updatedAt: Date; };
|
export type User = Profile & { id: string; createdAt: Date; updatedAt: Date };
|
||||||
|
|
||||||
export interface Session {
|
export interface Session {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
|
@ -70,7 +72,7 @@ export const MetamigoAdapter = (config: IAppConfig): Adapter => {
|
||||||
json: "force",
|
json: "force",
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getAdapter(_appOptions: AppOptions) {
|
function getAdapter(): Adapter {
|
||||||
async function createUser(profile: Profile) {
|
async function createUser(profile: Profile) {
|
||||||
try {
|
try {
|
||||||
if (!profile.createdBy) profile = { ...profile, createdBy: "nextauth" };
|
if (!profile.createdBy) profile = { ...profile, createdBy: "nextauth" };
|
||||||
|
|
@ -106,19 +108,23 @@ export const MetamigoAdapter = (config: IAppConfig): Adapter => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getUserByProviderAccountId(
|
async function getUserByAccount({
|
||||||
providerId: string,
|
providerAccountId,
|
||||||
providerAccountId: string
|
provider,
|
||||||
) {
|
}: {
|
||||||
|
providerAccountId: string;
|
||||||
|
provider: string;
|
||||||
|
}) {
|
||||||
try {
|
try {
|
||||||
const { payload } = await wreck.get(
|
const { payload } = await wreck.get(
|
||||||
`getUserByProviderAccountId/${providerId}/${providerAccountId}`
|
`getUserByAccount/${provider}/${providerAccountId}`
|
||||||
);
|
);
|
||||||
|
|
||||||
return payload;
|
return payload;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (Boom.isBoom(error, 404)) return null;
|
if (Boom.isBoom(error, 404)) return null;
|
||||||
throw new Error("GET_USER_BY_PROVIDER_ACCOUNT_ID");
|
console.log(error);
|
||||||
|
throw new Error("GET_USER_BY_ACCOUNT");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -134,52 +140,46 @@ export const MetamigoAdapter = (config: IAppConfig): Adapter => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function linkAccount(
|
async function linkAccount(account: AdapterAccount) {
|
||||||
userId: string,
|
|
||||||
providerId: string,
|
|
||||||
providerType: string,
|
|
||||||
providerAccountId: string,
|
|
||||||
refreshToken: string,
|
|
||||||
accessToken: string,
|
|
||||||
accessTokenExpires: number
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
const payload = {
|
await wreck.put("linkAccount", { payload: account } as any);
|
||||||
userId,
|
} catch (error) {
|
||||||
providerId,
|
console.log(error);
|
||||||
providerType,
|
|
||||||
providerAccountId: `${providerAccountId}`, // must be a string
|
|
||||||
refreshToken,
|
|
||||||
accessToken,
|
|
||||||
accessTokenExpires,
|
|
||||||
};
|
|
||||||
await wreck.put("linkAccount", {
|
|
||||||
payload,
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
throw new Error("LINK_ACCOUNT_ERROR");
|
throw new Error("LINK_ACCOUNT_ERROR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createSession(user: User) {
|
async function createSession(user: User) {
|
||||||
try {
|
try {
|
||||||
const { payload } = await wreck.post("createSession", {
|
const { payload }: { payload: AdapterSession } = await wreck.post(
|
||||||
payload: user,
|
"createSession",
|
||||||
});
|
{
|
||||||
|
payload: user,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
payload.expires = new Date(payload.expires);
|
||||||
return payload;
|
return payload;
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
throw new Error("CREATE_SESSION_ERROR");
|
throw new Error("CREATE_SESSION_ERROR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getSession(sessionToken: string) {
|
async function getSessionAndUser(sessionToken: string) {
|
||||||
try {
|
try {
|
||||||
const { payload } = await wreck.get(`getSession/${sessionToken}`);
|
const { payload }: { payload: any } = await wreck.get(
|
||||||
return payload;
|
`getSessionAndUser/${sessionToken}`
|
||||||
|
);
|
||||||
|
const {
|
||||||
|
session,
|
||||||
|
user,
|
||||||
|
}: { session: AdapterSession; user: AdapterUser } = payload;
|
||||||
|
session.expires = new Date(session.expires);
|
||||||
|
return { session, user };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
if (Boom.isBoom(error, 404)) return null;
|
if (Boom.isBoom(error, 404)) return null;
|
||||||
throw new Error("GET_SESSION_ERROR");
|
throw new Error("GET_SESSION_AND_USER_ERROR");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,21 +213,18 @@ export const MetamigoAdapter = (config: IAppConfig): Adapter => {
|
||||||
createUser,
|
createUser,
|
||||||
getUser,
|
getUser,
|
||||||
getUserByEmail,
|
getUserByEmail,
|
||||||
getUserByProviderAccountId,
|
getUserByAccount,
|
||||||
updateUser,
|
updateUser,
|
||||||
// deleteUser,
|
// deleteUser,
|
||||||
linkAccount,
|
linkAccount,
|
||||||
// unlinkAccount,
|
// unlinkAccount,
|
||||||
createSession,
|
createSession,
|
||||||
getSession,
|
getSessionAndUser,
|
||||||
updateSession,
|
updateSession,
|
||||||
deleteSession,
|
deleteSession,
|
||||||
// @ts-expect-error: Type error
|
// @ts-expect-error: Type error
|
||||||
} as AdapterInstance<Profile, User, Session, unknown>;
|
} as AdapterInstance<Profile, User, Session, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return getAdapter();
|
||||||
// @ts-expect-error: non-existent property
|
|
||||||
getAdapter,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,8 @@ export const E164Regex = /^\+[1-9]\d{1,14}$/;
|
||||||
/**
|
/**
|
||||||
* Returns true if the number is a valid E164 number
|
* Returns true if the number is a valid E164 number
|
||||||
*/
|
*/
|
||||||
export const isValidE164Number = (phoneNumber: string) => E164Regex.test(phoneNumber);
|
export const isValidE164Number = (phoneNumber: string) =>
|
||||||
|
E164Regex.test(phoneNumber);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a phone number approximation, will clean out whitespace and punctuation.
|
* Given a phone number approximation, will clean out whitespace and punctuation.
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "metamigo-frontend",
|
"name": "@digiresilience/metamigo-frontend",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -38,7 +38,7 @@
|
||||||
"test": "echo no tests",
|
"test": "echo no tests",
|
||||||
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.graphql && next lint && prettier --ignore-path .eslintignore \"**/*.{js,jsx,ts,tsx,graphql,md}\" --write",
|
"lint": "eslint --ext .js,.jsx,.ts,.tsx,.graphql && next lint && prettier --ignore-path .eslintignore \"**/*.{js,jsx,ts,tsx,graphql,md}\" --write",
|
||||||
"fix:lint": "eslint --ext .js,.jsx,.ts,.tsx,.graphql --fix",
|
"fix:lint": "eslint --ext .js,.jsx,.ts,.tsx,.graphql --fix",
|
||||||
"fmt": "prettier --ignore-path .eslintignore \"**/*.{js,jsx,ts,tsx,graphql,md}\" --list-different"
|
"fmt": "prettier --ignore-path .eslintignore \"**/*.{js,jsx,ts,tsx,graphql,md}\" --write"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next/eslint-plugin-next": "^13.4.4",
|
"@next/eslint-plugin-next": "^13.4.4",
|
||||||
|
|
|
||||||
|
|
@ -60,38 +60,20 @@ const nextAuthOptions = (config: IAppConfig, req: NextApiRequest) => {
|
||||||
return {
|
return {
|
||||||
secret: nextAuth.secret,
|
secret: nextAuth.secret,
|
||||||
session: {
|
session: {
|
||||||
jwt: true,
|
strategy: "database",
|
||||||
maxAge: 8 * 60 * 60, // 8 hours
|
maxAge: 8 * 60 * 60, // 8 hours
|
||||||
},
|
},
|
||||||
jwt: {
|
jwt: {
|
||||||
secret: nextAuth.secret,
|
secret: nextAuth.secret,
|
||||||
encryption: false,
|
|
||||||
signingKey: nextAuth.signingKey,
|
|
||||||
encryptionKey: nextAuth.encryptionKey,
|
|
||||||
},
|
},
|
||||||
providers,
|
providers,
|
||||||
adapter,
|
adapter,
|
||||||
callbacks: {
|
callbacks: {
|
||||||
async session(session: any, token: any) {
|
async session({ session, user }: any) {
|
||||||
// make the user id available in the react client
|
session.user.id = user.id;
|
||||||
session.user.id = token.userId;
|
session.user.userRole = user.userRole;
|
||||||
return session;
|
return session;
|
||||||
},
|
},
|
||||||
async jwt(token: any, user: any) {
|
|
||||||
const isSignIn = Boolean(user);
|
|
||||||
// Add auth_time to token on signin in
|
|
||||||
if (isSignIn) {
|
|
||||||
// not sure what this does
|
|
||||||
// if (!token.aud) token.aud;
|
|
||||||
|
|
||||||
token.aud = nextAuth.audience;
|
|
||||||
token.picture = user.avatar;
|
|
||||||
token.userId = user.id;
|
|
||||||
token.role = user.userRole ? `app_${user.userRole}` : "app_anonymous";
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ export default createProxyMiddleware({
|
||||||
target:
|
target:
|
||||||
process.env.NODE_ENV === "production"
|
process.env.NODE_ENV === "production"
|
||||||
? "http://metamigo-api:3001"
|
? "http://metamigo-api:3001"
|
||||||
: "http://localhost:3001",
|
: "http://127.0.0.1:3001",
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
pathRewrite: { "^/graphql": "/graphql" },
|
pathRewrite: { "^/graphql": "/graphql" },
|
||||||
xfwd: true,
|
xfwd: true,
|
||||||
|
|
@ -20,8 +20,6 @@ export default createProxyMiddleware({
|
||||||
let token = req.cookies["__Secure-next-auth.session-token"];
|
let token = req.cookies["__Secure-next-auth.session-token"];
|
||||||
if (!token) token = req.cookies["next-auth.session-token"];
|
if (!token) token = req.cookies["next-auth.session-token"];
|
||||||
|
|
||||||
// console.log(req.body);
|
|
||||||
// if (req.body.query) console.log(req.body.query);
|
|
||||||
if (token) {
|
if (token) {
|
||||||
proxyReq.setHeader("authorization", `Bearer ${token}`);
|
proxyReq.setHeader("authorization", `Bearer ${token}`);
|
||||||
proxyReq.removeHeader("cookie");
|
proxyReq.removeHeader("cookie");
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
FROM node:20-bullseye as builder
|
|
||||||
|
|
||||||
ARG METAMIGO_DIR=/opt/metamigo
|
|
||||||
RUN mkdir -p ${METAMIGO_DIR}/
|
|
||||||
WORKDIR ${METAMIGO_DIR}
|
|
||||||
COPY package.json tsconfig.json ${METAMIGO_DIR}/
|
|
||||||
COPY . ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
RUN npx --no-install tsc --build --verbose
|
|
||||||
RUN npm install
|
|
||||||
RUN npm run build
|
|
||||||
|
|
||||||
RUN rm -Rf ./node_modules
|
|
||||||
|
|
||||||
FROM node:20-bullseye as clean
|
|
||||||
ARG METAMIGO_DIR=/opt/metamigo
|
|
||||||
|
|
||||||
COPY --from=builder ${METAMIGO_DIR} ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
RUN rm -Rf ./node_modules
|
|
||||||
|
|
||||||
FROM node:20-bullseye as pristine
|
|
||||||
LABEL maintainer="Abel Luck <abel@guardianproject.info>"
|
|
||||||
|
|
||||||
RUN DEBIAN_FRONTEND=noninteractive apt-get update && \
|
|
||||||
apt-get install -y --no-install-recommends --fix-missing \
|
|
||||||
postgresql-client dumb-init ffmpeg
|
|
||||||
|
|
||||||
ARG METAMIGO_DIR=/opt/metamigo
|
|
||||||
ENV METAMIGO_DIR ${METAMIGO_DIR}
|
|
||||||
RUN mkdir -p ${METAMIGO_DIR}
|
|
||||||
RUN chown -R node:node ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
COPY docker-entrypoint.sh /docker-entrypoint.sh
|
|
||||||
RUN chmod +x /docker-entrypoint.sh
|
|
||||||
|
|
||||||
COPY --from=clean ${METAMIGO_DIR}/ ${METAMIGO_DIR}/
|
|
||||||
|
|
||||||
WORKDIR ${METAMIGO_DIR}
|
|
||||||
|
|
||||||
USER node
|
|
||||||
|
|
||||||
EXPOSE 3000
|
|
||||||
EXPOSE 3001
|
|
||||||
EXPOSE 3002
|
|
||||||
ENV PORT 3000
|
|
||||||
ENV NODE_ENV production
|
|
||||||
|
|
||||||
ARG BUILD_DATE
|
|
||||||
ARG VCS_REF
|
|
||||||
ARG VCS_URL="https://gitlab.com/digiresilience/link/metamigo"
|
|
||||||
ARG VERSION
|
|
||||||
LABEL org.label-schema.schema-version="1.0"
|
|
||||||
LABEL org.label-schema.name="digiresilience.org/link/metamigo"
|
|
||||||
LABEL org.label-schema.description="part of CDR Link"
|
|
||||||
LABEL org.label-schema.build-date=$BUILD_DATE
|
|
||||||
LABEL org.label-schema.vcs-url=$VCS_URL
|
|
||||||
LABEL org.label-schema.vcs-ref=$VCS_REF
|
|
||||||
LABEL org.label-schema.version=$VERSION
|
|
||||||
|
|
||||||
ENTRYPOINT ["/docker-entrypoint.sh"]
|
|
||||||
|
|
@ -2,10 +2,10 @@ import * as Worker from "graphile-worker";
|
||||||
import { parseCronItems } from "graphile-worker";
|
import { parseCronItems } from "graphile-worker";
|
||||||
import { defState } from "@digiresilience/montar";
|
import { defState } from "@digiresilience/montar";
|
||||||
import config from "@digiresilience/metamigo-config";
|
import config from "@digiresilience/metamigo-config";
|
||||||
import { initPgp } from "./db";
|
import { initPgp } from "./db.js";
|
||||||
import logger from "./logger";
|
import logger from "./logger.js";
|
||||||
import workerUtils from "./utils";
|
import workerUtils from "./utils.js";
|
||||||
import { assertFfmpegAvailable } from "./lib/media-convert";
|
import { assertFfmpegAvailable } from "./lib/media-convert.js";
|
||||||
|
|
||||||
const logFactory = (scope: any) => (level: any, message: any, meta: any) => {
|
const logFactory = (scope: any) => (level: any, message: any, meta: any) => {
|
||||||
const pinoLevel = level === "warning" ? "warn" : level;
|
const pinoLevel = level === "warning" ? "warn" : level;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "metamigo-worker",
|
"name": "@digiresilience/metamigo-worker",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"main": "build/main/index.js",
|
"main": "build/main/index.js",
|
||||||
|
"type": "module",
|
||||||
"author": "Abel Luck <abel@guardianproject.info>",
|
"author": "Abel Luck <abel@guardianproject.info>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|
@ -44,14 +45,12 @@
|
||||||
"doc": "yarn run doc:html",
|
"doc": "yarn run doc:html",
|
||||||
"fix:lint": "eslint src --ext .ts --fix",
|
"fix:lint": "eslint src --ext .ts --fix",
|
||||||
"fix:prettier": "prettier \"src/**/*.ts\" --write",
|
"fix:prettier": "prettier \"src/**/*.ts\" --write",
|
||||||
"worker": "NODE_ENV=development yarn cli worker",
|
|
||||||
"test:jest": "JEST_CIRCUS=1 jest --coverage --forceExit --detectOpenHandles --reporters=default --reporters=jest-junit",
|
"test:jest": "JEST_CIRCUS=1 jest --coverage --forceExit --detectOpenHandles --reporters=default --reporters=jest-junit",
|
||||||
"test:jest-verbose": "yarn test:jest --verbose --silent=false",
|
"test:jest-verbose": "yarn test:jest --verbose --silent=false",
|
||||||
"test": "yarn test:jest",
|
"test": "yarn test:jest",
|
||||||
"lint": "yarn lint:lint && yarn lint:prettier",
|
"lint": "yarn lint:lint && yarn lint:prettier",
|
||||||
"lint:lint": "eslint src --ext .ts",
|
"lint:lint": "eslint src --ext .ts",
|
||||||
"lint:prettier": "prettier \"src/**/*.ts\" --list-different",
|
"lint:prettier": "prettier \"src/**/*.ts\" --list-different",
|
||||||
"watch:build": "tsc -p tsconfig.json -w",
|
|
||||||
"watch:test": "yarn test:jest --watchAll"
|
"watch:test": "yarn test:jest --watchAll"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
version: "3.4"
|
version: "3.4"
|
||||||
|
|
||||||
|
x-global-vars:
|
||||||
|
&common-global-variables
|
||||||
|
TZ: Etc/UTC
|
||||||
|
|
||||||
x-zammad-vars:
|
x-zammad-vars:
|
||||||
&common-zammad-variables
|
&common-zammad-variables
|
||||||
MEMCACHE_SERVERS: "zammad-memcached:11211"
|
MEMCACHE_SERVERS: "zammad-memcached:11211"
|
||||||
|
|
@ -41,10 +45,12 @@ services:
|
||||||
zammad-elasticsearch:
|
zammad-elasticsearch:
|
||||||
container_name: zammad-elasticsearch
|
container_name: zammad-elasticsearch
|
||||||
environment:
|
environment:
|
||||||
- discovery.type=single-node
|
discovery.type: single-node
|
||||||
- ES_JAVA_OPTS=-Xms750m -Xmx750m
|
ES_JAVA_OPTS: -Xms750m -Xmx750m
|
||||||
- xpack.security.enabled=false
|
xpack.security.enabled: false
|
||||||
|
<<: *common-global-variables
|
||||||
build: ./docker/elasticsearch
|
build: ./docker/elasticsearch
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/zammad-elasticsearch
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
volumes:
|
volumes:
|
||||||
- elasticsearch-data:/usr/share/elasticsearch/data
|
- elasticsearch-data:/usr/share/elasticsearch/data
|
||||||
|
|
@ -56,10 +62,11 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- zammad-postgresql
|
- zammad-postgresql
|
||||||
environment:
|
environment:
|
||||||
<<: *common-zammad-variables
|
<<: [ *common-zammad-variables, *common-global-variables ]
|
||||||
POSTGRESQL_USER: zammad
|
POSTGRESQL_USER: zammad
|
||||||
POSTGRESQL_PASS: ${ZAMMAD_DATABASE_PASSWORD}
|
POSTGRESQL_PASS: ${ZAMMAD_DATABASE_PASSWORD}
|
||||||
build: ./docker/zammad
|
build: ./docker/zammad
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/zammad
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
volumes:
|
volumes:
|
||||||
- zammad-data:/opt/zammad
|
- zammad-data:/opt/zammad
|
||||||
|
|
@ -68,7 +75,10 @@ services:
|
||||||
container_name: zammad-memcached
|
container_name: zammad-memcached
|
||||||
command: memcached -m 256M
|
command: memcached -m 256M
|
||||||
build: ./docker/memcached
|
build: ./docker/memcached
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/zammad-memcached
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
|
environment:
|
||||||
|
<<: *common-global-variables
|
||||||
|
|
||||||
zammad-nginx:
|
zammad-nginx:
|
||||||
platform: linux/x86_64
|
platform: linux/x86_64
|
||||||
|
|
@ -81,22 +91,27 @@ services:
|
||||||
depends_on:
|
depends_on:
|
||||||
- zammad-railsserver
|
- zammad-railsserver
|
||||||
build: ./docker/zammad
|
build: ./docker/zammad
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/zammad
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
environment:
|
environment:
|
||||||
|
<<: *common-global-variables
|
||||||
NGINX_SERVER_SCHEME: https
|
NGINX_SERVER_SCHEME: https
|
||||||
VIRTUAL_HOST: ${ZAMMAD_VIRTUAL_HOST}
|
VIRTUAL_HOST: ${ZAMMAD_VIRTUAL_HOST}
|
||||||
VIRTUAL_PORT: 8080
|
VIRTUAL_PORT: 8080
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
- zammad-data:/opt/zammad
|
- zammad-data:/opt/zammad
|
||||||
|
|
||||||
zammad-postgresql:
|
zammad-postgresql:
|
||||||
container_name: zammad-postgresql
|
container_name: zammad-postgresql
|
||||||
environment:
|
environment:
|
||||||
- POSTGRES_USER=zammad
|
<<: *common-global-variables
|
||||||
- POSTGRES_PASSWORD=${ZAMMAD_DATABASE_PASSWORD}
|
POSTGRES_USER: zammad
|
||||||
|
POSTGRES_PASSWORD: ${ZAMMAD_DATABASE_PASSWORD}
|
||||||
build: ./docker/postgresql
|
build: ./docker/postgresql
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/postgresql
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:5432:5432
|
||||||
volumes:
|
volumes:
|
||||||
- postgresql-data:/var/lib/postgresql/data
|
- postgresql-data:/var/lib/postgresql/data
|
||||||
|
|
||||||
|
|
@ -108,8 +123,10 @@ services:
|
||||||
- zammad-memcached
|
- zammad-memcached
|
||||||
- zammad-postgresql
|
- zammad-postgresql
|
||||||
- zammad-redis
|
- zammad-redis
|
||||||
environment: *common-zammad-variables
|
environment:
|
||||||
|
<<: [ *common-global-variables, *common-zammad-variables ]
|
||||||
build: ./docker/zammad
|
build: ./docker/zammad
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/zammad
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
volumes:
|
volumes:
|
||||||
- zammad-data:/opt/zammad
|
- zammad-data:/opt/zammad
|
||||||
|
|
@ -117,7 +134,10 @@ services:
|
||||||
zammad-redis:
|
zammad-redis:
|
||||||
container_name: zammad-redis
|
container_name: zammad-redis
|
||||||
build: ./docker/redis
|
build: ./docker/redis
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/zammad-redis
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
|
environment:
|
||||||
|
<<: *common-global-variables
|
||||||
|
|
||||||
zammad-scheduler:
|
zammad-scheduler:
|
||||||
platform: linux/x86_64
|
platform: linux/x86_64
|
||||||
|
|
@ -127,8 +147,10 @@ services:
|
||||||
- zammad-memcached
|
- zammad-memcached
|
||||||
- zammad-railsserver
|
- zammad-railsserver
|
||||||
- zammad-redis
|
- zammad-redis
|
||||||
environment: *common-zammad-variables
|
environment:
|
||||||
|
<<: [ *common-global-variables, *common-zammad-variables ]
|
||||||
build: ./docker/zammad
|
build: ./docker/zammad
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/zammad
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
volumes:
|
volumes:
|
||||||
- zammad-data:/opt/zammad
|
- zammad-data:/opt/zammad
|
||||||
|
|
@ -141,67 +163,100 @@ services:
|
||||||
- zammad-memcached
|
- zammad-memcached
|
||||||
- zammad-railsserver
|
- zammad-railsserver
|
||||||
- zammad-redis
|
- zammad-redis
|
||||||
environment: *common-zammad-variables
|
environment:
|
||||||
|
<<: [ *common-global-variables, *common-zammad-variables ]
|
||||||
build: ./docker/zammad
|
build: ./docker/zammad
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/zammad
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
volumes:
|
volumes:
|
||||||
- zammad-data:/opt/zammad
|
- zammad-data:/opt/zammad
|
||||||
|
|
||||||
# metamigo-api:
|
opensearch:
|
||||||
# build: ./apps/metamigo-api
|
container_name: opensearch
|
||||||
# container_name: metamigo-api
|
build: ./docker/opensearch
|
||||||
# restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
# command: [ "api" ]
|
volumes:
|
||||||
# expose:
|
- opensearch-data:/usr/share/opensearch/data
|
||||||
# - "3001"
|
|
||||||
# environment: *common-metamigo-variables
|
|
||||||
# volumes:
|
|
||||||
# - ./signald:/signald
|
|
||||||
|
|
||||||
# metamigo-frontend:
|
opensearch-dashboards:
|
||||||
# build: ./apps/metamigo-frontend
|
container_name: opensearch-dashboards
|
||||||
# container_name: metamigo-frontend
|
build: ./docker/opensearch-dashboards
|
||||||
# restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
# command: [ "frontend" ]
|
|
||||||
# expose:
|
|
||||||
# - "3000"
|
|
||||||
# ports:
|
|
||||||
# - 127.0.0.1:8002:3000
|
|
||||||
# environment:
|
|
||||||
# <<: *common-metamigo-variables
|
|
||||||
# VIRTUAL_HOST: ${METAMIGO_VIRTUAL_HOST}
|
|
||||||
# VIRTUAL_PORT: 3000
|
|
||||||
|
|
||||||
# metamigo-worker:
|
metamigo-postgresql:
|
||||||
# build: ./apps/metamigo-worker
|
build: ./docker/postgresql
|
||||||
# container_name: metamigo-worker
|
image: registry.gitlab.com/digiresilience/link/link-stack/postgresql
|
||||||
# restart: ${RESTART}
|
container_name: metamigo-postgresql
|
||||||
# command: [ "worker" ]
|
restart: ${RESTART}
|
||||||
# environment: *common-metamigo-variables
|
volumes:
|
||||||
|
- metamigo-data:/var/lib/postgresql/data
|
||||||
|
- ./scripts/bootstrap-metamigo.sh:/docker-entrypoint-initdb.d/bootstrap-metamigo.sh
|
||||||
|
environment:
|
||||||
|
<<: *common-metamigo-variables
|
||||||
|
POSTGRES_PASSWORD: ${METAMIGO_DATABASE_ROOT_PASSWORD}
|
||||||
|
POSTGRES_USER: "root"
|
||||||
|
POSTGRES_DB: "metamigo"
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:5433:5432
|
||||||
|
|
||||||
# metamigo-postgresql:
|
metamigo-api:
|
||||||
# build: ./docker/postgresql
|
build:
|
||||||
# restart: ${RESTART}
|
context: .
|
||||||
# volumes:
|
dockerfile: ./apps/metamigo-cli/Dockerfile
|
||||||
# - metamigo-data:/var/lib/postgresql/data
|
image: registry.gitlab.com/digiresilience/link/link-stack/metamigo-cli
|
||||||
# - ./scripts/bootstrap-metamigo.sh:/docker-entrypoint-initdb.d/bootstrap-metamigo.sh
|
container_name: metamigo-api
|
||||||
# environment:
|
restart: ${RESTART}
|
||||||
# <<: *common-metamigo-variables
|
command: [ "api" ]
|
||||||
# POSTGRES_PASSWORD: ${METAMIGO_DATABASE_ROOT_PASSWORD}
|
ports:
|
||||||
# POSTGRES_USER: "root"
|
- 127.0.0.1:8004:3001
|
||||||
# POSTGRES_DB: "metamigo"
|
environment: *common-metamigo-variables
|
||||||
# expose:
|
volumes:
|
||||||
# - "5432"
|
- ./signald-state:/signald
|
||||||
# ports:
|
depends_on:
|
||||||
# - 127.0.0.1:5432:5432
|
- metamigo-postgresql
|
||||||
|
- signald
|
||||||
|
|
||||||
|
metamigo-frontend:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./apps/metamigo-frontend/Dockerfile
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/metamigo-frontend
|
||||||
|
container_name: metamigo-frontend
|
||||||
|
restart: ${RESTART}
|
||||||
|
command: [ "frontend" ]
|
||||||
|
expose:
|
||||||
|
- "3000"
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:8002:3000
|
||||||
|
depends_on:
|
||||||
|
- metamigo-api
|
||||||
|
environment:
|
||||||
|
<<: *common-metamigo-variables
|
||||||
|
VIRTUAL_HOST: ${METAMIGO_VIRTUAL_HOST}
|
||||||
|
VIRTUAL_PORT: 3000
|
||||||
|
|
||||||
|
metamigo-worker:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./apps/metamigo-cli/Dockerfile
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/metamigo-cli
|
||||||
|
container_name: metamigo-worker
|
||||||
|
restart: ${RESTART}
|
||||||
|
command: [ "worker" ]
|
||||||
|
environment: *common-metamigo-variables
|
||||||
|
depends_on:
|
||||||
|
- metamigo-api
|
||||||
|
|
||||||
signald:
|
signald:
|
||||||
container_name: signald
|
container_name: signald
|
||||||
build: ./docker/signald
|
build: ./docker/signald
|
||||||
|
image: registry.gitlab.com/digiresilience/link/link-stack/signald
|
||||||
restart: ${RESTART}
|
restart: ${RESTART}
|
||||||
user: ${CURRENT_UID}
|
user: ${CURRENT_UID}
|
||||||
volumes:
|
volumes:
|
||||||
- ../signald:/signald
|
- ./signald-state:/signald
|
||||||
|
environment:
|
||||||
|
<<: *common-global-variables
|
||||||
|
|
||||||
# nginx-proxy:
|
# nginx-proxy:
|
||||||
# container_name: nginx-proxy
|
# container_name: nginx-proxy
|
||||||
|
|
@ -265,3 +320,5 @@ volumes:
|
||||||
driver: local
|
driver: local
|
||||||
metamigo-data:
|
metamigo-data:
|
||||||
driver: local
|
driver: local
|
||||||
|
opensearch-data:
|
||||||
|
driver: local
|
||||||
|
|
|
||||||
1
docker/opensearch-dashboards/Dockerfile
Normal file
1
docker/opensearch-dashboards/Dockerfile
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
FROM opensearchproject/opensearch-dashboards:2.8.0
|
||||||
1
docker/opensearch/Dockerfile
Normal file
1
docker/opensearch/Dockerfile
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
FROM opensearchproject/opensearch:2.8.0
|
||||||
1
docker/zammad/.gitignore
vendored
1
docker/zammad/.gitignore
vendored
|
|
@ -2,3 +2,4 @@ docker-compose-test.yml
|
||||||
*.zpm
|
*.zpm
|
||||||
*.zip
|
*.zip
|
||||||
addons
|
addons
|
||||||
|
!addons/.gitkeep
|
||||||
|
|
|
||||||
0
docker/zammad/addons/.gitkeep
Normal file
0
docker/zammad/addons/.gitkeep
Normal file
6705
package-lock.json
generated
6705
package-lock.json
generated
File diff suppressed because it is too large
Load diff
21
package.json
21
package.json
|
|
@ -4,7 +4,11 @@
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "dotenv -- turbo run dev"
|
"dev": "dotenv -- turbo run dev --concurrency 30",
|
||||||
|
"build": "turbo build --concurrency 30",
|
||||||
|
"dev:metamigo": "make dev-metamigo && dotenv -- turbo run dev --concurrency 30 --filter=!link --filter=!leafcutter",
|
||||||
|
"migrate": "dotenv -- npm run migrate --workspace=@digiresilience/metamigo-cli",
|
||||||
|
"fmt": "turbo run fmt"
|
||||||
},
|
},
|
||||||
"packageManager": "npm@9.3.1",
|
"packageManager": "npm@9.3.1",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
|
|
@ -16,7 +20,7 @@
|
||||||
"url": "git+https://gitlab.com/digiresilience/link/link-stack.git"
|
"url": "git+https://gitlab.com/digiresilience/link/link-stack.git"
|
||||||
},
|
},
|
||||||
"author": "Darren Clarke",
|
"author": "Darren Clarke",
|
||||||
"license": "ISC",
|
"dlicense": "ISC",
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@mui/styles": {
|
"@mui/styles": {
|
||||||
"react": "18.2.0"
|
"react": "18.2.0"
|
||||||
|
|
@ -24,11 +28,14 @@
|
||||||
"typeorm": {
|
"typeorm": {
|
||||||
"pg": "^8.11.0"
|
"pg": "^8.11.0"
|
||||||
},
|
},
|
||||||
"hapi-postgraphile": {
|
"graphql": "15.8.0"
|
||||||
"pg": "^8.11.0"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"prettier": "^2.8.8"
|
"prettier": "^2.8.8",
|
||||||
|
"dotenv-cli": "latest"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"npm": ">=9.6.7",
|
||||||
|
"node": ">=20"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -6,7 +6,7 @@ This is a plugin for hapi.js that exposes [NextAuth's database adapter](https://
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
import NextAuthPlugin from "@digiresilience/hapi-nextauth";
|
import NextAuthPlugin from "@digiresilience/hapi-nextauth";
|
||||||
import type { AdapterInstance } from "next-auth/adapters";
|
import type { AdapterInstance } from "next-auth/adapters";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,16 +3,18 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "a plugin for hapi.js that exposes NextAuth's database adapter via HTTP",
|
"description": "a plugin for hapi.js that exposes NextAuth's database adapter via HTTP",
|
||||||
"main": "build/main/index.js",
|
"main": "build/main/index.js",
|
||||||
|
"type": "module",
|
||||||
"author": "Abel Luck <abel@guardianproject.info>",
|
"author": "Abel Luck <abel@guardianproject.info>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"private": false,
|
"private": false,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jest": "^29.5.1",
|
|
||||||
"@hapi/basic": "^7.0.1",
|
"@hapi/basic": "^7.0.1",
|
||||||
"tsconfig-link": "*",
|
"@types/jest": "^29.5.1",
|
||||||
|
"babel-preset-link": "*",
|
||||||
"eslint-config-link": "*",
|
"eslint-config-link": "*",
|
||||||
"jest-config-link": "*",
|
"jest-config-link": "*",
|
||||||
"babel-preset-link": "*"
|
"tsc-watch": "^6.0.4",
|
||||||
|
"tsconfig-link": "*"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/hapi": "^21.3.2",
|
"@hapi/hapi": "^21.3.2",
|
||||||
|
|
@ -28,6 +30,6 @@
|
||||||
"lint": "eslint src --ext .ts",
|
"lint": "eslint src --ext .ts",
|
||||||
"lint-fmt": "prettier \"src/**/*.ts\" --list-different",
|
"lint-fmt": "prettier \"src/**/*.ts\" --list-different",
|
||||||
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
||||||
"watch:build": "tsc -p tsconfig.json -w"
|
"dev": "tsc-watch --build --noClear"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import * as Hoek from "@hapi/hoek";
|
import * as Hoek from "@hapi/hoek";
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
|
|
||||||
import { NextAuthPluginOptions } from "./types";
|
import { NextAuthPluginOptions } from "./types.js";
|
||||||
import * as Routes from "./routes";
|
import * as Routes from "./routes.js";
|
||||||
|
|
||||||
const minimumProfileSchema = Joi.object()
|
const minimumProfileSchema = Joi.object()
|
||||||
.keys({
|
.keys({
|
||||||
|
|
@ -13,7 +13,7 @@ const minimumProfileSchema = Joi.object()
|
||||||
|
|
||||||
const minimumUserSchema = Joi.object()
|
const minimumUserSchema = Joi.object()
|
||||||
.keys({
|
.keys({
|
||||||
id: Joi.string().required(),
|
userId: Joi.string().required(),
|
||||||
email: Joi.string().email().required(),
|
email: Joi.string().email().required(),
|
||||||
})
|
})
|
||||||
.unknown(true);
|
.unknown(true);
|
||||||
|
|
@ -62,12 +62,11 @@ const register = async (
|
||||||
server: Hapi.Server,
|
server: Hapi.Server,
|
||||||
pluginOpts?: any
|
pluginOpts?: any
|
||||||
): Promise<void> => {
|
): Promise<void> => {
|
||||||
const options: any =
|
const options: any = Hoek.applyToDefaults(
|
||||||
Hoek.applyToDefaults(
|
// a little type gymnastics here to workaround poor typing
|
||||||
// a little type gymnastics here to workaround poor typing
|
defaultOptions as any,
|
||||||
defaultOptions as any,
|
pluginOpts
|
||||||
pluginOpts
|
) as any;
|
||||||
) as any;
|
|
||||||
|
|
||||||
if (!options.nextAuthAdapterFactory) {
|
if (!options.nextAuthAdapterFactory) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
|
|
@ -98,5 +97,5 @@ const nextAuthPlugin = {
|
||||||
version: "0.0.3",
|
version: "0.0.3",
|
||||||
};
|
};
|
||||||
|
|
||||||
export * from "./types";
|
export * from "./types.js";
|
||||||
export default nextAuthPlugin;
|
export default nextAuthPlugin;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/* eslint-disable unicorn/no-null */
|
/* eslint-disable unicorn/no-null */
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import { ResponseToolkit, ResponseObject } from "@hapi/hapi";
|
import { ResponseToolkit, ResponseObject } from "@hapi/hapi";
|
||||||
|
|
||||||
|
|
@ -93,13 +93,13 @@ export const register = async <TUser, TProfile>(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: `${basePath}/getUserByProviderAccountId/{providerId}/{providerAccountId}`,
|
path: `${basePath}/getUserByAccount/{provider}/{providerAccountId}`,
|
||||||
options: {
|
options: {
|
||||||
auth,
|
auth,
|
||||||
tags,
|
tags,
|
||||||
validate: {
|
validate: {
|
||||||
params: {
|
params: {
|
||||||
providerId: Joi.string(),
|
provider: Joi.string(),
|
||||||
providerAccountId: Joi.string(),
|
providerAccountId: Joi.string(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -107,10 +107,10 @@ export const register = async <TUser, TProfile>(
|
||||||
request: Hapi.Request,
|
request: Hapi.Request,
|
||||||
h: ResponseToolkit
|
h: ResponseToolkit
|
||||||
): Promise<ResponseObject> {
|
): Promise<ResponseObject> {
|
||||||
const { providerId, providerAccountId } = request.params;
|
const { provider, providerAccountId } = request.params;
|
||||||
const r = await opts
|
const r = await opts
|
||||||
.nextAuthAdapterFactory(request)
|
.nextAuthAdapterFactory(request)
|
||||||
.getUserByProviderAccountId(providerId, providerAccountId);
|
.getUserByAccount(provider, providerAccountId);
|
||||||
if (!r) return h.response().code(404);
|
if (!r) return h.response().code(404);
|
||||||
return h.response(r as object);
|
return h.response(r as object);
|
||||||
},
|
},
|
||||||
|
|
@ -148,14 +148,15 @@ export const register = async <TUser, TProfile>(
|
||||||
tags,
|
tags,
|
||||||
validate: {
|
validate: {
|
||||||
payload: Joi.object({
|
payload: Joi.object({
|
||||||
userId,
|
// https://next-auth.js.org/getting-started/upgrade-v4#schema-changes
|
||||||
providerId: Joi.string(),
|
userId: Joi.string().required(),
|
||||||
providerType: Joi.string(),
|
provider: Joi.string().required(),
|
||||||
providerAccountId: Joi.string(),
|
type: Joi.string().required(),
|
||||||
refreshToken: Joi.string().optional().allow(null),
|
providerAccountId: Joi.string().required(),
|
||||||
accessToken: Joi.string().optional().allow(null),
|
refresh_token: Joi.string().optional().allow(null),
|
||||||
accessTokenExpires: Joi.number().optional().allow(null),
|
access_token: Joi.string().optional().allow(null),
|
||||||
}).options({ presence: "required" }),
|
expires_at: Joi.number().optional().allow(null),
|
||||||
|
}).unknown(true),
|
||||||
},
|
},
|
||||||
async handler(
|
async handler(
|
||||||
request: Hapi.Request,
|
request: Hapi.Request,
|
||||||
|
|
@ -193,7 +194,11 @@ export const register = async <TUser, TProfile>(
|
||||||
auth,
|
auth,
|
||||||
tags,
|
tags,
|
||||||
validate: {
|
validate: {
|
||||||
payload: user,
|
payload: Joi.object({
|
||||||
|
userId: Joi.string().required(),
|
||||||
|
sessionToken: Joi.string().required(),
|
||||||
|
expires: Joi.string().isoDate().required(),
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
async handler(
|
async handler(
|
||||||
request: Hapi.Request,
|
request: Hapi.Request,
|
||||||
|
|
@ -210,7 +215,7 @@ export const register = async <TUser, TProfile>(
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
method: "GET",
|
method: "GET",
|
||||||
path: `${basePath}/getSession/{sessionToken}`,
|
path: `${basePath}/getSessionAndUser/{sessionToken}`,
|
||||||
options: {
|
options: {
|
||||||
auth,
|
auth,
|
||||||
tags,
|
tags,
|
||||||
|
|
@ -226,7 +231,7 @@ export const register = async <TUser, TProfile>(
|
||||||
const token = request.params.sessionToken;
|
const token = request.params.sessionToken;
|
||||||
const r = await opts
|
const r = await opts
|
||||||
.nextAuthAdapterFactory(request)
|
.nextAuthAdapterFactory(request)
|
||||||
.getSession(token);
|
.getSessionAndUser(token);
|
||||||
if (!r) return h.response().code(404);
|
if (!r) return h.response().code(404);
|
||||||
return h.response(r as object);
|
return h.response(r as object);
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,7 @@ import type { Adapter } from "next-auth/adapters";
|
||||||
import type { NumberSchema, StringSchema, ObjectSchema } from "joi";
|
import type { NumberSchema, StringSchema, ObjectSchema } from "joi";
|
||||||
import type { Request } from "@hapi/hapi";
|
import type { Request } from "@hapi/hapi";
|
||||||
|
|
||||||
export type AdapterFactory = (
|
export type AdapterFactory = (request: Request) => Adapter;
|
||||||
request: Request
|
|
||||||
) => Adapter;
|
|
||||||
|
|
||||||
export interface NextAuthPluginOptions {
|
export interface NextAuthPluginOptions {
|
||||||
nextAuthAdapterFactory: Adapter;
|
nextAuthAdapterFactory: Adapter;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "tsconfig-link",
|
"extends": "tsconfig-link",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"outDir": "build/main",
|
"outDir": "build/main",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,12 @@
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "a hapi.js plugin for pg-promise",
|
"description": "a hapi.js plugin for pg-promise",
|
||||||
"main": "build/main/index.js",
|
"main": "build/main/index.js",
|
||||||
|
"type": "module",
|
||||||
"author": "Abel Luck <abel@guardianproject.info>",
|
"author": "Abel Luck <abel@guardianproject.info>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
"private": false,
|
"private": false,
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"tsc-watch": "^6.0.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@hapi/hapi": "^21.3.2",
|
"@hapi/hapi": "^21.3.2",
|
||||||
|
|
@ -20,6 +22,6 @@
|
||||||
"test": "jest --coverage --forceExit --detectOpenHandles --reporters=default --reporters=jest-junit",
|
"test": "jest --coverage --forceExit --detectOpenHandles --reporters=default --reporters=jest-junit",
|
||||||
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
||||||
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
||||||
"watch:build": "tsc -p tsconfig.json -w"
|
"dev": "tsc-watch --build --noClear"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import pgPromise from "pg-promise";
|
import pgPromise from "pg-promise";
|
||||||
import * as pgMonitor from "pg-monitor";
|
import pgMonitor from "pg-monitor";
|
||||||
import type { IConnectionParameters } from "pg-promise/typescript/pg-subset";
|
import type { IConnectionParameters } from "pg-promise/typescript/pg-subset";
|
||||||
import type { IMain, IInitOptions } from "pg-promise";
|
import type { IMain, IInitOptions } from "pg-promise";
|
||||||
import { IPGPPluginOptions, ExtendedProtocol } from "./types";
|
import { IPGPPluginOptions, ExtendedProtocol } from "./types.js";
|
||||||
import { Plugin } from "@hapi/hapi/lib/types/plugin";
|
import { Plugin } from "@hapi/hapi/lib/types/plugin";
|
||||||
|
|
||||||
export * from "./types";
|
export * from "./types.js";
|
||||||
|
|
||||||
export const startDiagnostics = <T>(
|
export const startDiagnostics = <T>(
|
||||||
logSql: boolean,
|
logSql: boolean,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
{
|
{
|
||||||
"extends": "tsconfig-link",
|
"extends": "tsconfig-link",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"composite": true,
|
||||||
"incremental": true,
|
"incremental": true,
|
||||||
"outDir": "build/main",
|
"outDir": "build/main",
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
require('eslint-config-link/patch/modern-module-resolution');
|
|
||||||
module.exports = {
|
|
||||||
extends: [
|
|
||||||
"eslint-config-link/profile/node",
|
|
||||||
"eslint-config-link/profile/typescript"
|
|
||||||
],
|
|
||||||
parserOptions: { tsconfigRootDir: __dirname }
|
|
||||||
};
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"presets": [
|
|
||||||
"babel-preset-link"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "build/main/index.js",
|
"main": "build/main/index.js",
|
||||||
|
"type": "module",
|
||||||
"types": "build/main/index.d.ts",
|
"types": "build/main/index.d.ts",
|
||||||
"author": "Abel Luck <abel@guardianproject.info>",
|
"author": "Abel Luck <abel@guardianproject.info>",
|
||||||
"license": "AGPL-3.0-or-later",
|
"license": "AGPL-3.0-or-later",
|
||||||
|
|
@ -13,7 +14,7 @@
|
||||||
"fmt": "prettier \"src/**/*.ts\" --write",
|
"fmt": "prettier \"src/**/*.ts\" --write",
|
||||||
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
"lint": "eslint src --ext .ts && prettier \"src/**/*.ts\" --list-different",
|
||||||
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
"doc": "typedoc src/ --exclude '**/*.test.ts' --exclude '**/*.spec.ts' --name $npm_package_name --readme README.md --target es2019 --mode file --out build/docs",
|
||||||
"watch:build": "tsc -p tsconfig.json -w"
|
"dev": "tsc-watch --build --noClear "
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/figlet": "^1.5.6",
|
"@types/figlet": "^1.5.6",
|
||||||
|
|
@ -22,6 +23,7 @@
|
||||||
"@types/uuid": "^9.0.1",
|
"@types/uuid": "^9.0.1",
|
||||||
"camelcase-keys": "^8.0.2",
|
"camelcase-keys": "^8.0.2",
|
||||||
"pg-monitor": "^2.0.0",
|
"pg-monitor": "^2.0.0",
|
||||||
|
"tsc-watch": "^6.0.4",
|
||||||
"typedoc": "^0.24.7",
|
"typedoc": "^0.24.7",
|
||||||
"typescript": "^5.0.4"
|
"typescript": "^5.0.4"
|
||||||
},
|
},
|
||||||
|
|
@ -55,6 +57,7 @@
|
||||||
"next-auth": "^4.22.1",
|
"next-auth": "^4.22.1",
|
||||||
"pg-promise": "^11.4.3",
|
"pg-promise": "^11.4.3",
|
||||||
"pino": "^8.14.1",
|
"pino": "^8.14.1",
|
||||||
|
"pino-pretty": "^10.0.0",
|
||||||
"prom-client": "^14.x.x",
|
"prom-client": "^14.x.x",
|
||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ConvictSchema } from "./types";
|
import { ConvictSchema } from "./types.js";
|
||||||
|
|
||||||
export interface IAppMetaConfig {
|
export interface IAppMetaConfig {
|
||||||
name: string;
|
name: string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ConvictSchema } from "./types";
|
import { ConvictSchema } from "./types.js";
|
||||||
|
|
||||||
export interface ISessionConfig {
|
export interface ISessionConfig {
|
||||||
sessionMaxAgeSeconds: number;
|
sessionMaxAgeSeconds: number;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ConvictSchema } from "./types";
|
import { ConvictSchema } from "./types.js";
|
||||||
|
|
||||||
export interface ICorsConfig {
|
export interface ICorsConfig {
|
||||||
allowedMethods: Array<string>;
|
allowedMethods: Array<string>;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
import type { Format } from "convict";
|
import type { Format } from "convict";
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
import convict, { SchemaObj } from "convict";
|
import convict, { SchemaObj } from "convict";
|
||||||
import { IServerConfig, ServerConfig } from "./server";
|
import { IServerConfig, ServerConfig } from "./server.js";
|
||||||
import { IMetricsConfig, MetricsConfig } from "./metrics-server";
|
import { IMetricsConfig, MetricsConfig } from "./metrics-server.js";
|
||||||
import { IAppMetaConfig, AppMetaConfig } from "./app-meta";
|
import { IAppMetaConfig, AppMetaConfig } from "./app-meta.js";
|
||||||
import { ICorsConfig, CorsConfig } from "./cors";
|
import { ICorsConfig, CorsConfig } from "./cors.js";
|
||||||
import { ILoggingConfig, LoggingConfig } from "./logging";
|
import { ILoggingConfig, LoggingConfig } from "./logging.js";
|
||||||
import { ExtendedConvict } from "./types";
|
import { ExtendedConvict } from "./types.js";
|
||||||
import { MetamigoConvictFormats } from "./formats";
|
import { MetamigoConvictFormats } from "./formats.js";
|
||||||
|
|
||||||
type IEnvConfig = "production" | "development" | "test";
|
type IEnvConfig = "production" | "development" | "test";
|
||||||
|
|
||||||
|
|
@ -51,10 +51,10 @@ export type IMetamigoConvict = ExtendedConvict<IMetamigoConfig>;
|
||||||
|
|
||||||
export type { IMetamigoConfig };
|
export type { IMetamigoConfig };
|
||||||
|
|
||||||
export * from "./formats";
|
export * from "./formats.js";
|
||||||
export * from "./generate";
|
export * from "./generate.js";
|
||||||
export * from "./print";
|
export * from "./print.js";
|
||||||
export * from "./types";
|
export * from "./types.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Loads your applications configuration from environment variables and configuration files (see METAMIGO_CONFIG).
|
* Loads your applications configuration from environment variables and configuration files (see METAMIGO_CONFIG).
|
||||||
|
|
@ -137,8 +137,8 @@ export const loadConfiguration = async <T extends IMetamigoConfig>(
|
||||||
return c.getProperties();
|
return c.getProperties();
|
||||||
};
|
};
|
||||||
|
|
||||||
export { type IServerConfig } from "./server";
|
export { type IServerConfig } from "./server.js";
|
||||||
export { type IMetricsConfig } from "./metrics-server";
|
export { type IMetricsConfig } from "./metrics-server.js";
|
||||||
export { type IAppMetaConfig } from "./app-meta";
|
export { type IAppMetaConfig } from "./app-meta.js";
|
||||||
export { type ICorsConfig } from "./cors";
|
export { type ICorsConfig } from "./cors.js";
|
||||||
export { type ILoggingConfig } from "./logging";
|
export { type ILoggingConfig } from "./logging.js";
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ConvictSchema } from "./types";
|
import { ConvictSchema } from "./types.js";
|
||||||
|
|
||||||
export interface ILoggingConfig {
|
export interface ILoggingConfig {
|
||||||
level: string;
|
level: string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ConvictSchema } from "./types";
|
import { ConvictSchema } from "./types.js";
|
||||||
|
|
||||||
export interface IMetricsConfig {
|
export interface IMetricsConfig {
|
||||||
address: string;
|
address: string;
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { ConvictSchema } from "./types";
|
import { ConvictSchema } from "./types.js";
|
||||||
|
|
||||||
export interface IServerConfig {
|
export interface IServerConfig {
|
||||||
address: string;
|
address: string;
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
/* eslint-disable @typescript-eslint/no-explicit-any,max-params */
|
/* eslint-disable @typescript-eslint/no-explicit-any,max-params */
|
||||||
import * as Boom from "@hapi/boom";
|
import * as Boom from "@hapi/boom";
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import { CrudRepository } from "../records/crud-repository";
|
import { CrudRepository } from "../records/crud-repository.js";
|
||||||
import { createResponse } from "../helpers/response";
|
import { createResponse } from "../helpers/response.js";
|
||||||
import {
|
import {
|
||||||
PgRecordInfo,
|
PgRecordInfo,
|
||||||
UnsavedR,
|
UnsavedR,
|
||||||
SavedR,
|
SavedR,
|
||||||
KeyType,
|
KeyType,
|
||||||
} from "../records/record-info";
|
} from "../records/record-info.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,16 @@
|
||||||
/* eslint-disable unicorn/no-null,max-params */
|
/* eslint-disable unicorn/no-null,max-params */
|
||||||
import { createHash, randomBytes } from "node:crypto";
|
import { createHash, randomBytes } from "node:crypto";
|
||||||
import omit from "lodash/omit";
|
import omit from "lodash/omit.js";
|
||||||
import type { IMetamigoRepositories } from "../records";
|
import { IMetamigoRepositories, idKeysOf } from "../records/index.js";
|
||||||
import type { UnsavedAccount } from "../records/account";
|
import type { UnsavedAccount } from "../records/account.js";
|
||||||
import type { UserId, UnsavedUser, SavedUser } from "../records/user";
|
import type { UserId, UnsavedUser, SavedUser } from "../records/user.js";
|
||||||
import type { UnsavedSession, SavedSession } from "../records/session";
|
import type { UnsavedSession, SavedSession } from "../records/session.js";
|
||||||
|
import {
|
||||||
|
AdapterAccount,
|
||||||
|
AdapterSession,
|
||||||
|
AdapterUser,
|
||||||
|
} from "next-auth/adapters.js";
|
||||||
|
import { ReadableStreamDefaultController } from "stream/web";
|
||||||
|
|
||||||
// Sessions expire after 30 days of being idle
|
// Sessions expire after 30 days of being idle
|
||||||
export const defaultSessionMaxAge = 30 * 24 * 60 * 60 * 1000;
|
export const defaultSessionMaxAge = 30 * 24 * 60 * 60 * 1000;
|
||||||
|
|
@ -23,7 +29,7 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
|
||||||
private repos: TRepositories,
|
private repos: TRepositories,
|
||||||
private readonly sessionMaxAge = defaultSessionMaxAge,
|
private readonly sessionMaxAge = defaultSessionMaxAge,
|
||||||
private readonly sessionUpdateAge = defaulteSessionUpdateAge
|
private readonly sessionUpdateAge = defaulteSessionUpdateAge
|
||||||
) { }
|
) {}
|
||||||
|
|
||||||
async createUser(profile: UnsavedUser): Promise<SavedUser> {
|
async createUser(profile: UnsavedUser): Promise<SavedUser> {
|
||||||
// @ts-expect-error Typescript doesn't like lodash's omit()
|
// @ts-expect-error Typescript doesn't like lodash's omit()
|
||||||
|
|
@ -56,12 +62,12 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getUserByProviderAccountId(
|
async getUserByAccount(
|
||||||
providerId: string,
|
provider: string,
|
||||||
providerAccountId: string
|
providerAccountId: string
|
||||||
): Promise<SavedUser | null> {
|
): Promise<SavedUser | null> {
|
||||||
const account = await this.repos.accounts.findBy({
|
const account = await this.repos.accounts.findBy({
|
||||||
compoundId: getCompoundId(providerId, providerAccountId),
|
compoundId: getCompoundId(provider, providerAccountId),
|
||||||
});
|
});
|
||||||
if (!account) return null;
|
if (!account) return null;
|
||||||
|
|
||||||
|
|
@ -72,15 +78,16 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
|
||||||
return this.repos.users.update(user);
|
return this.repos.users.update(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
async linkAccount(
|
async linkAccount(adapterAccount: AdapterAccount): Promise<void> {
|
||||||
userId: string,
|
const {
|
||||||
providerId: string,
|
userId,
|
||||||
providerType: string,
|
access_token: accessToken,
|
||||||
providerAccountId: string,
|
refresh_token: refreshToken,
|
||||||
refreshToken: string,
|
provider: providerId,
|
||||||
accessToken: string,
|
providerAccountId,
|
||||||
accessTokenExpires: number
|
expires_at: accessTokenExpires,
|
||||||
): Promise<void> {
|
type: providerType,
|
||||||
|
} = adapterAccount;
|
||||||
const exists = await this.repos.users.existsById({ id: userId });
|
const exists = await this.repos.users.existsById({ id: userId });
|
||||||
if (!exists) return;
|
if (!exists) return;
|
||||||
const account: UnsavedAccount = {
|
const account: UnsavedAccount = {
|
||||||
|
|
@ -109,7 +116,13 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
createSession(user: SavedUser): Promise<SavedSession> {
|
createSession({
|
||||||
|
sessionToken,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
sessionToken: string;
|
||||||
|
userId: string;
|
||||||
|
}): Promise<SavedSession> {
|
||||||
let expires;
|
let expires;
|
||||||
if (this.sessionMaxAge) {
|
if (this.sessionMaxAge) {
|
||||||
const dateExpires = new Date(Date.now() + this.sessionMaxAge);
|
const dateExpires = new Date(Date.now() + this.sessionMaxAge);
|
||||||
|
|
@ -118,22 +131,42 @@ export class NextAuthAdapter<TRepositories extends IMetamigoRepositories> {
|
||||||
|
|
||||||
const session: UnsavedSession = {
|
const session: UnsavedSession = {
|
||||||
expires,
|
expires,
|
||||||
userId: user.id,
|
userId,
|
||||||
sessionToken: randomToken(),
|
sessionToken,
|
||||||
|
//sessionToken: randomToken(),
|
||||||
accessToken: randomToken(),
|
accessToken: randomToken(),
|
||||||
};
|
};
|
||||||
|
|
||||||
return this.repos.sessions.insert(session);
|
return this.repos.sessions.insert(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSession(sessionToken: string): Promise<SavedSession | null> {
|
async getSessionAndUser(
|
||||||
|
sessionToken: string
|
||||||
|
): Promise<{ session: AdapterSession; user: any } | null> {
|
||||||
const session = await this.repos.sessions.findBy({ sessionToken });
|
const session = await this.repos.sessions.findBy({ sessionToken });
|
||||||
|
if (!session) return null;
|
||||||
if (session && session.expires && new Date() > session.expires) {
|
if (session && session.expires && new Date() > session.expires) {
|
||||||
this.repos.sessions.remove(session);
|
this.repos.sessions.remove(session);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return session;
|
const user = await this.repos.users.findById({ id: session.userId });
|
||||||
|
if (!user) return null;
|
||||||
|
|
||||||
|
const adapterSession: AdapterSession = {
|
||||||
|
userId: session.userId,
|
||||||
|
expires: session.expires,
|
||||||
|
sessionToken: sessionToken,
|
||||||
|
};
|
||||||
|
|
||||||
|
const adapterUser: any = {
|
||||||
|
id: user.id,
|
||||||
|
email: user.email,
|
||||||
|
emailVerified: user.emailVerified,
|
||||||
|
userRole: user.userRole
|
||||||
|
};
|
||||||
|
|
||||||
|
return { session: adapterSession, user: adapterUser };
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSession(
|
async updateSession(
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ import PinoPlugin from "hapi-pino";
|
||||||
import { createServer as createPrometheusServer } from "@promster/server";
|
import { createServer as createPrometheusServer } from "@promster/server";
|
||||||
import { createHttpTerminator } from "http-terminator";
|
import { createHttpTerminator } from "http-terminator";
|
||||||
|
|
||||||
import { getPrettyPrint } from "./logger";
|
import { configureLogger } from "./logger.js";
|
||||||
import RequestIdPlugin from "./plugins/request-id";
|
import RequestIdPlugin from "./plugins/request-id.js";
|
||||||
import StatusPlugin from "./plugins/status";
|
import StatusPlugin from "./plugins/status.js";
|
||||||
import ConfigPlugin from "./plugins/config";
|
import ConfigPlugin from "./plugins/config.js";
|
||||||
import { IMetamigoConfig } from "./config";
|
import { IMetamigoConfig } from "./config/index.js";
|
||||||
|
|
||||||
export interface Server {
|
export interface Server {
|
||||||
hapiServer: Hapi.Server;
|
hapiServer: Hapi.Server;
|
||||||
|
|
@ -79,8 +79,8 @@ export const defaultPlugins = <T extends IMetamigoConfig>(
|
||||||
{
|
{
|
||||||
plugin: PinoPlugin,
|
plugin: PinoPlugin,
|
||||||
options: {
|
options: {
|
||||||
prettyPrint: getPrettyPrint(config),
|
|
||||||
level,
|
level,
|
||||||
|
instance: configureLogger(config),
|
||||||
logRequestStart,
|
logRequestStart,
|
||||||
logRequestComplete,
|
logRequestComplete,
|
||||||
logPayload: logRequestPayload,
|
logPayload: logRequestPayload,
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import process from "node:process";
|
import process from "node:process";
|
||||||
import * as Hapi from "@hapi/hapi";
|
import * as Hapi from "@hapi/hapi";
|
||||||
import * as Joi from "joi";
|
import Joi from "joi";
|
||||||
import Hoek from "@hapi/hoek";
|
import * as Hoek from "@hapi/hoek";
|
||||||
import * as Boom from "@hapi/boom";
|
import * as Boom from "@hapi/boom";
|
||||||
|
|
||||||
export interface HapiValidationError extends Joi.ValidationError {
|
export interface HapiValidationError extends Joi.ValidationError {
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
export * from "./config";
|
export * from "./config/index.js";
|
||||||
export * from "./controllers/crud-controller";
|
export * from "./controllers/crud-controller.js";
|
||||||
export * from "./controllers/nextauth-adapter";
|
export * from "./controllers/nextauth-adapter.js";
|
||||||
export * from "./hapi";
|
export * from "./hapi.js";
|
||||||
export * from "./helpers";
|
export * from "./helpers/index.js";
|
||||||
export * from "./helpers/response";
|
export * from "./helpers/response.js";
|
||||||
export * from "./helpers/validation-error";
|
export * from "./helpers/validation-error.js";
|
||||||
export * from "./logger";
|
export * from "./logger.js";
|
||||||
export * from "./records";
|
export * from "./records/index.js";
|
||||||
|
|
||||||
import * as pino from "pino";
|
import * as pino from "pino";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,5 @@
|
||||||
import pino, { LoggerOptions } from "pino";
|
import pino, { LoggerOptions } from "pino";
|
||||||
import { IMetamigoConfig } from "./config";
|
import { IMetamigoConfig } from "./config/index.js";
|
||||||
|
|
||||||
export const getPrettyPrint = <T extends IMetamigoConfig>(
|
|
||||||
config: T
|
|
||||||
): boolean => {
|
|
||||||
const { prettyPrint } = config.logging;
|
|
||||||
if (prettyPrint === "auto") return config?.isDev || false;
|
|
||||||
return prettyPrint === true;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const configureLogger = <T extends IMetamigoConfig>(
|
export const configureLogger = <T extends IMetamigoConfig>(
|
||||||
config: T
|
config: T
|
||||||
|
|
@ -15,6 +7,9 @@ export const configureLogger = <T extends IMetamigoConfig>(
|
||||||
const { level, redact } = config.logging;
|
const { level, redact } = config.logging;
|
||||||
const options: LoggerOptions = {
|
const options: LoggerOptions = {
|
||||||
level,
|
level,
|
||||||
|
transport: {
|
||||||
|
target: "pino-pretty",
|
||||||
|
},
|
||||||
redact: {
|
redact: {
|
||||||
paths: redact,
|
paths: redact,
|
||||||
remove: true,
|
remove: true,
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue