Compare commits

..

34 Commits

Author SHA1 Message Date
58363c2c1d Revert "Move CI to Git submodule"
All checks were successful
ci/woodpecker/push/build/1 Pipeline was successful
ci/woodpecker/push/build/2 Pipeline was successful
ci/woodpecker/push/manifest Pipeline was successful
This reverts commit 86d0b37713.
2024-08-20 12:59:25 +03:00
86d0b37713 Move CI to Git submodule 2024-08-20 12:50:14 +03:00
de2c82617f Use mirror.gcr.io for prod Docker stage as well
All checks were successful
ci/woodpecker/push/build/1 Pipeline was successful
ci/woodpecker/push/build/2 Pipeline was successful
ci/woodpecker/push/manifest Pipeline was successful
2024-08-15 23:00:19 +03:00
f6e2278c81 Use mirror.gcr.io
Some checks failed
ci/woodpecker/push/build/1 Pipeline failed
ci/woodpecker/push/build/2 Pipeline failed
ci/woodpecker/push/manifest unknown status
2024-08-15 22:56:41 +03:00
f36f893e70 Update CI
Some checks failed
ci/woodpecker/push/build/2 Pipeline failed
ci/woodpecker/push/build/1 Pipeline was successful
ci/woodpecker/push/manifest unknown status
2024-08-15 22:49:30 +03:00
Sergo
63abd46364 chore(cve): update packages
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-08-11 12:17:03 +03:00
6f465d79a3 Update config/prod.js
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2024-07-27 20:50:05 +00:00
78d2ec5ce6 Update config/prod.js
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2024-07-27 20:43:41 +00:00
6cecbec592 Revert "fix(oidc): temporarily disable pkce"
All checks were successful
continuous-integration/drone/push Build is passing
This reverts commit ed9559efe5.
2024-03-26 15:27:58 +02:00
ed9559efe5 fix(oidc): temporarily disable pkce
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 14:47:45 +02:00
4d17c3bcff fix(oidc): hardcode scopes
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 14:39:19 +02:00
d0e5faa040 fix(oidc): update oidc client
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 14:00:30 +02:00
4f76cce8d3 fix(oidc): fix oidc conf
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 13:48:14 +02:00
e7ad775573 fix(oidc): fix oidc conf
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 13:24:54 +02:00
5d9ca61ecc fix(ci): fix drone ci
Some checks failed
continuous-integration/drone/push Build is failing
2024-03-26 13:04:59 +02:00
29480105c5 fix(oidc): get all oidc parameters from env
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone Build is failing
ci/woodpecker/manual/woodpecker Pipeline was successful
2024-03-25 15:10:26 +02:00
Sergo
caca4f3409 update readme
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-24 17:24:19 +02:00
Sergo
4b78256ae2 update readme, logger logs in json format
Some checks failed
continuous-integration/drone/push Build is failing
2024-02-24 17:21:32 +02:00
Sergo
c6e74cca90 add card-style template for mobile view
All checks were successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2024-02-04 01:48:14 +02:00
Sergo
1ca64bc925 render data with Handlebars template, support tags
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-04 00:07:01 +02:00
Sergo
b772c83c53 update bootstrap cdn
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-12-01 22:30:16 +02:00
76f1c43858 adj: prevent deleting non-managed addresses
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/manual/woodpecker Pipeline was successful
2023-08-13 19:23:38 +03:00
a2d344ceb4 minor fixes
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-13 18:46:56 +03:00
50d07820dc add redis dependency
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-12 12:44:40 +03:00
7a41437121 rm redis pass
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-12 11:24:53 +03:00
049e90540d Merge branch 'master' into dev
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
ci/woodpecker/push/woodpecker Pipeline was successful
2023-08-12 11:18:07 +03:00
c93df5d573 Merge pull request 'store express sessions in redis, delete only aliases from preferred domain' (#3) from dev into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: k-space/walias#3
2023-08-12 08:14:48 +00:00
e8d1ba9c96 add redis creds
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-12 10:56:16 +03:00
c0a310b5ff update TODO
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-06 09:34:26 +03:00
d9014d2a54 merge master
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-06 09:30:44 +03:00
feb5e5b4ca add prettier 2023-08-06 09:21:17 +03:00
01931cb238 Merge pull request 'dev-prettier' (#4) from dev-prettier into master
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
Reviewed-on: k-space/walias#4
2023-08-06 06:15:01 +00:00
cc453e2337 sanitize aliases response, hide delete button for non-prefereable domain aliases
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-05 15:02:44 +03:00
fcb5780ef9 store express sessions in redis
Some checks failed
ci/woodpecker/push/woodpecker Pipeline was successful
ci/woodpecker/pr/woodpecker Pipeline failed
2023-08-05 11:48:46 +03:00
21 changed files with 588 additions and 259 deletions

View File

@ -8,3 +8,4 @@ deployment.yaml
Dockerfile
readme.md
.git
lib

View File

@ -1,35 +0,0 @@
kind: pipeline
type: kubernetes
name: build
steps:
- name: docker
image: harbor.k-space.ee/k-space/drone-kaniko
settings:
repo: ${DRONE_REPO}
tags: ${DRONE_BRANCH}
registry: harbor.codemowers.eu
username:
from_secret: docker_username
password:
from_secret: docker_password
- name: test
image: harbor.codemowers.eu/msergo/walias:${DRONE_COMMIT_SHA}
settings:
registry: harbor.codemowers.eu
username:
from_secret: docker_username
password:
from_secret: docker_password
commands:
- npm test
# services:
# - name: mysql
# image: mysql:8
# environment:
# MYSQL_ROOT_PASSWORD: dev
# MYSQL_DATABASE: sky
image_pull_secrets:
- dockerconfigjson

View File

@ -1,13 +0,0 @@
pipeline:
build:
image: plugins/kaniko
settings:
repo: harbor.k-space.ee/${CI_REPO}
tags: latest
registry: harbor.k-space.ee
username:
from_secret: docker_username
password:
from_secret: docker_password
when:
branch: master

26
.woodpecker/build.yaml Normal file
View File

@ -0,0 +1,26 @@
---
matrix:
ARCH:
- amd64
- arm64
steps:
- name: build
image: woodpeckerci/plugin-kaniko
backend_options:
kubernetes:
nodeSelector:
kubernetes.io/arch: ${ARCH}
tolerations:
- key: arch
operator: Equal
value: ${ARCH}
effect: NoSchedule
settings:
repo: ${CI_REPO}
registry: harbor.k-space.ee
tags: latest-${ARCH}
username:
from_secret: docker_username
password:
from_secret: docker_password

31
.woodpecker/manifest.yaml Normal file
View File

@ -0,0 +1,31 @@
---
skip_clone: true
steps:
- name: manifest
image: mirror.gcr.io/mplatform/manifest-tool:alpine-v2.1.6
secrets:
- docker_username
- docker_password
commands:
- set -u
- ls -lash
- env
- |
cat << EOF > spec.yaml
image: "harbor.k-space.ee/${CI_REPO}:latest"
manifests:
- image: "harbor.k-space.ee/${CI_REPO}:latest-amd64"
platform:
architecture: amd64
os: linux
- image: "harbor.k-space.ee/${CI_REPO}:latest-arm64"
platform:
architecture: arm64
os: linux
EOF
- /manifest-tool --username $docker_username --password $docker_password push from-spec spec.yaml > stdout
- cat stdout
depends_on:
- build

View File

@ -1,4 +1,4 @@
FROM node:18-alpine as dev
FROM mirror.gcr.io/library/node:18-alpine as dev
RUN apk add netcat-openbsd
RUN npm config set update-notifier false
@ -12,7 +12,7 @@ RUN npm run compile
ENTRYPOINT npm run start
FROM node:18-alpine AS prod
FROM mirror.gcr.io/node:18-alpine AS prod
RUN npm config set update-notifier false
WORKDIR /app

View File

@ -5,6 +5,7 @@
"origins": [
"http://localhost:3030"
],
"sessionSecret": "test",
"paginate": {
"default": 10,
"max": 50
@ -14,5 +15,8 @@
"token": "aaaaa",
"domain": "test-codemowers.eu",
"preferredDomain": "k-space.ee"
},
"redis": {
"url": "redis://localhost:6379"
}
}

View File

@ -1,14 +1,27 @@
module.exports = {
clientUrl: process.env.CLIENT_URL,
sessionSecret: process.env.SESSION_SECRET,
oidc: {
gatewayUri: process.env.OIDC_GATEWAY_URI,
gatewayAuthUri: process.env.OIDC_GATEWAY_AUTH_URI,
gatewayTokenUri: process.env.OIDC_GATEWAY_TOKEN_URI,
gatewayUserinfoUri: process.env.OIDC_GATEWAY_USERINFO_URI,
clientId: process.env.OIDC_CLIENT_ID,
clientSecret: process.env.OIDC_CLIENT_SECRET,
redirectUris: process.env.OIDC_REDIRECT_URIS
redirectUris: process.env.OIDC_REDIRECT_URIS,
scopes: 'openid profile',
grantTypes: process.env.OIDC_GRANT_TYPES,
signedResponseAlg: process.env.OIDC_ID_TOKEN_SIGNED_RESPONSE_ALG,
codeChallengeMethod: 'S256',
responseTypes: process.env.OIDC_RESPONSE_TYPES,
authMethod: process.env.OIDC_TOKEN_ENDPOINT_AUTH_METHOD,
},
wildDuck: {
url: process.env.WILDDUCK_URL,
token: process.env.WILDDUCK_TOKEN,
domain: process.env.WILDDUCK_DOMAIN
}
domain: process.env.WILDDUCK_DOMAIN,
},
redis: {
url: process.env.REDIS_URL,
},
};

View File

@ -98,6 +98,11 @@ spec:
secretKeyRef:
name: walias-secrets
key: WILDDUCK_DOMAIN
- name: REDIS_URL
valueFrom:
secretKeyRef:
name: redis-walias-cache-owner-secrets
key: REDIS_MASTER_URI
envFrom:
- secretRef:
name: oidc-client-walias-owner-secrets

334
package-lock.json generated
View File

@ -1,11 +1,11 @@
{
"name": "walias",
"name": "wildflock",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "walias",
"name": "wildflock",
"version": "1.0.0",
"dependencies": {
"@faker-js/faker": "^8.0.2",
@ -22,9 +22,12 @@
"axios": "^1.4.0",
"compression": "^1.7.4",
"config": "^3.3.9",
"connect-redis": "^7.1.0",
"cookie-parser": "^1.4.6",
"express-session": "^1.17.3",
"openid-client": "^5.4.3",
"ioredis": "^5.3.2",
"openid-client": "^5.6.5",
"redis": "^4.6.7",
"winston": "^3.10.0"
},
"devDependencies": {
@ -34,6 +37,7 @@
"@types/express-session": "^1.17.7",
"@types/mocha": "^10.0.1",
"@types/node": "^20.4.5",
"@types/redis": "^4.0.11",
"cross-env": "^7.0.3",
"husky": "^8.0.3",
"lint-staged": "^13.2.3",
@ -413,6 +417,11 @@
"url": "https://github.com/sponsors/daffl"
}
},
"node_modules/@ioredis/commands": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz",
"integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg=="
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
@ -438,6 +447,59 @@
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"node_modules/@redis/bloom": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/client": {
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz",
"integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==",
"dependencies": {
"cluster-key-slot": "1.1.2",
"generic-pool": "3.9.0",
"yallist": "4.0.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/@redis/graph": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz",
"integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/json": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz",
"integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/search": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz",
"integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@redis/time-series": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz",
"integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
@ -610,6 +672,16 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw=="
},
"node_modules/@types/redis": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-4.0.11.tgz",
"integrity": "sha512-bI+gth8La8Wg/QCR1+V1fhrL9+LZUSWfcqpOj2Kc80ZQ4ffbdL173vQd5wovmoV9i071FU9oP2g6etLuEwb6Rg==",
"deprecated": "This is a stub types definition. redis provides its own type definitions, so you do not need this installed.",
"dev": true,
"dependencies": {
"redis": "*"
}
},
"node_modules/@types/send": {
"version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
@ -867,11 +939,11 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"version": "1.6.7",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
"integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
"dependencies": {
"follow-redirects": "^1.15.0",
"follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@ -931,12 +1003,12 @@
}
},
"node_modules/body-parser": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz",
"integrity": "sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw==",
"version": "1.20.2",
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
"integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.4",
"content-type": "~1.0.5",
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
@ -944,7 +1016,7 @@
"iconv-lite": "0.4.24",
"on-finished": "2.4.1",
"qs": "6.11.0",
"raw-body": "2.5.1",
"raw-body": "2.5.2",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
@ -971,12 +1043,12 @@
}
},
"node_modules/braces": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"dependencies": {
"fill-range": "^7.0.1"
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
@ -1229,6 +1301,14 @@
"node": ">=0.8"
}
},
"node_modules/cluster-key-slot": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/color": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
@ -1340,6 +1420,17 @@
"node": ">= 10.0.0"
}
},
"node_modules/connect-redis": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-7.1.0.tgz",
"integrity": "sha512-UaqO1EirWjON2ENsyau7N5lbkrdYBpS6mYlXSeff/OYXsd6EGZ+SXSmNPoljL2PSua8fgjAEaldSA73PMZQ9Eg==",
"engines": {
"node": ">=16"
},
"peerDependencies": {
"express-session": ">=1"
}
},
"node_modules/content-disposition": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -1379,9 +1470,9 @@
}
},
"node_modules/cookie": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.5.0.tgz",
"integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==",
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
@ -1501,6 +1592,14 @@
"node": ">=0.4.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -1566,9 +1665,9 @@
}
},
"node_modules/engine.io": {
"version": "6.5.1",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.1.tgz",
"integrity": "sha512-mGqhI+D7YxS9KJMppR6Iuo37Ed3abhU8NdfgSvJSDUafQutrN+sPTncJYTyM9+tkhSmWodKtVYGPPHyXJEwEQA==",
"version": "6.5.5",
"resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz",
"integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==",
"dependencies": {
"@types/cookie": "^0.4.1",
"@types/cors": "^2.8.12",
@ -1578,17 +1677,17 @@
"cookie": "~0.4.1",
"cors": "~2.8.5",
"debug": "~4.3.1",
"engine.io-parser": "~5.1.0",
"ws": "~8.11.0"
"engine.io-parser": "~5.2.1",
"ws": "~8.17.1"
},
"engines": {
"node": ">=10.0.0"
"node": ">=10.2.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.1.0.tgz",
"integrity": "sha512-enySgNiK5tyZFynt3z7iqBR+Bto9EVVVvDFuTT0ioHCGbzirZVGDGiQjZzEp8hWl6hd5FSVytJGuScX1C1C35w==",
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz",
"integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==",
"engines": {
"node": ">=10.0.0"
}
@ -1724,16 +1823,16 @@
}
},
"node_modules/express": {
"version": "4.18.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz",
"integrity": "sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ==",
"version": "4.19.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz",
"integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==",
"dependencies": {
"accepts": "~1.3.8",
"array-flatten": "1.1.1",
"body-parser": "1.20.1",
"body-parser": "1.20.2",
"content-disposition": "0.5.4",
"content-type": "~1.0.4",
"cookie": "0.5.0",
"cookie": "0.6.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "2.0.0",
@ -1868,9 +1967,9 @@
}
},
"node_modules/fill-range": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"dependencies": {
"to-regex-range": "^5.0.1"
@ -1927,9 +2026,9 @@
"integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw=="
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.6",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
"integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
"funding": [
{
"type": "individual",
@ -1999,6 +2098,14 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/generic-pool": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
"engines": {
"node": ">= 4"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -2274,6 +2381,50 @@
"node": ">= 0.10"
}
},
"node_modules/ioredis": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.3.2.tgz",
"integrity": "sha512-1DKMMzlIHM02eBBVOFQ1+AolGjs6+xEcM4PDL7NqOS6szq7H9jSaEkIUH6/a5Hl241LzW6JLSiAbNvTQjUupUA==",
"dependencies": {
"@ioredis/commands": "^1.1.1",
"cluster-key-slot": "^1.1.0",
"debug": "^4.3.4",
"denque": "^2.1.0",
"lodash.defaults": "^4.2.0",
"lodash.isarguments": "^3.1.0",
"redis-errors": "^1.2.0",
"redis-parser": "^3.0.0",
"standard-as-callback": "^2.1.0"
},
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/ioredis"
}
},
"node_modules/ioredis/node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/ioredis/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/ipaddr.js": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -2398,9 +2549,9 @@
"dev": true
},
"node_modules/jose": {
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz",
"integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==",
"version": "4.15.5",
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz",
"integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==",
"funding": {
"url": "https://github.com/sponsors/panva"
}
@ -2650,6 +2801,16 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
},
"node_modules/lodash.isarguments": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
"integrity": "sha512-chi4NHZlZqZD18a0imDHnZPrDeBbTtVN7GXMwuGdRH9qotxAjYs3aVLKc7zNOG9eddR5Ksd8rvFEBc9SsggPpg=="
},
"node_modules/log-symbols": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -3232,11 +3393,11 @@
}
},
"node_modules/openid-client": {
"version": "5.4.3",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.4.3.tgz",
"integrity": "sha512-sVQOvjsT/sbSfYsQI/9liWQGVZH/Pp3rrtlGEwgk/bbHfrUDZ24DN57lAagIwFtuEu+FM9Ev7r85s8S/yPjimQ==",
"version": "5.6.5",
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.5.tgz",
"integrity": "sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==",
"dependencies": {
"jose": "^4.14.4",
"jose": "^4.15.5",
"lru-cache": "^6.0.0",
"object-hash": "^2.2.0",
"oidc-token-hash": "^5.0.3"
@ -3478,9 +3639,9 @@
}
},
"node_modules/raw-body": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.1.tgz",
"integrity": "sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig==",
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
"integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
@ -3536,6 +3697,38 @@
"node": ">= 0.10"
}
},
"node_modules/redis": {
"version": "4.6.7",
"resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz",
"integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==",
"dependencies": {
"@redis/bloom": "1.2.0",
"@redis/client": "1.5.8",
"@redis/graph": "1.1.0",
"@redis/json": "1.0.4",
"@redis/search": "1.1.3",
"@redis/time-series": "1.0.4"
}
},
"node_modules/redis-errors": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
"integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==",
"engines": {
"node": ">=4"
}
},
"node_modules/redis-parser": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
"integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==",
"dependencies": {
"redis-errors": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -3851,13 +4044,35 @@
}
},
"node_modules/socket.io-adapter": {
"version": "2.5.2",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz",
"integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==",
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"dependencies": {
"ws": "~8.11.0"
"debug": "~4.3.4",
"ws": "~8.17.1"
}
},
"node_modules/socket.io-adapter/node_modules/debug": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io-adapter/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/socket.io-parser": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.4.tgz",
@ -3920,6 +4135,11 @@
"node": "*"
}
},
"node_modules/standard-as-callback": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
"integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A=="
},
"node_modules/statuses": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",
@ -4357,15 +4577,15 @@
"dev": true
},
"node_modules/ws": {
"version": "8.11.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
"version": "8.17.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
"integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {

View File

@ -1,5 +1,5 @@
{
"name": "walias",
"name": "wildflock",
"description": "Aliases for Wild Duck",
"version": "1.0.0",
"homepage": "",
@ -7,9 +7,16 @@
"keywords": [
"feathers"
],
"author": {
"url": "github.com/msergo"
"authors": [
{
"name": "Sergii",
"url": "https://github.com/msergo"
},
{
"name": "Erki",
"url": "https://github.com/veebkolm"
}
],
"contributors": [],
"bugs": {},
"engines": {
@ -55,9 +62,12 @@
"axios": "^1.4.0",
"compression": "^1.7.4",
"config": "^3.3.9",
"connect-redis": "^7.1.0",
"cookie-parser": "^1.4.6",
"express-session": "^1.17.3",
"openid-client": "^5.4.3",
"ioredis": "^5.3.2",
"openid-client": "^5.6.5",
"redis": "^4.6.7",
"winston": "^3.10.0"
},
"devDependencies": {
@ -67,6 +77,7 @@
"@types/express-session": "^1.17.7",
"@types/mocha": "^10.0.1",
"@types/node": "^20.4.5",
"@types/redis": "^4.0.11",
"cross-env": "^7.0.3",
"husky": "^8.0.3",
"lint-staged": "^13.2.3",

View File

@ -2,15 +2,16 @@
<html lang="en">
<head>
<title>walias</title>
<script src="index.js" async></script>
<title>WildDuck Aliases</title>
<meta name="description" content="Aliases for Wild Duck">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-4bw+/aepP/YC94hEpVNVgiZdgIC5+VKNBQNGCHeKRQN+PtmoHDEXuppvnDJzQIu9" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.1/dist/js/bootstrap.bundle.min.js"
integrity="sha384-HwwvtgBNo3bZJJLYd8oVXjrBZt8cqVSpeBNS5n7C8IVInixGAoxmnlMuBnhbgrkm"
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/handlebars@latest/dist/handlebars.js"></script>
<script src="index.js" async></script>
<style>
* {
margin: 0;
@ -50,11 +51,11 @@
</style>
</head>
<body >
<body>
<div class="wrapper">
<div class="header">
<img class="logo" src="logo.jpg">
<h1>Wildduck aliases</h1>
<h1>WildDuck Aliases</h1>
</div>
<div id="container"></div>
</div>

View File

@ -1,93 +1,98 @@
function renderAliases(aliases) {
const dataContainer = document.getElementById('container')
// Create table element ant populate with items
const table = document.createElement('table')
table.classList.add('table')
const thead = document.createElement('thead')
const tr = document.createElement('tr')
for (const value of ['Address', 'Created']) {
const th = document.createElement('th')
th.setAttribute('scope', 'col')
th.innerText = value
tr.appendChild(th)
}
thead.appendChild(tr)
table.appendChild(thead)
const tbody = document.createElement('tbody')
// sort by field created
aliases
function renderAliases(data) {
const dataContainer = document.getElementById('container');
const template = window.innerWidth > 768 ? Handlebars.compile(defaultTemplate) : Handlebars.compile(responsiveCardTemplate);
const html = template({
data: data
.sort((a, b) => new Date(a.created) - new Date(b.created))
.forEach(alias => {
const tr = document.createElement('tr')
const tdAddress = document.createElement('td')
// On click copy to clipboard
tdAddress.addEventListener('click', () => {
navigator.clipboard.writeText(alias.address)
})
tdAddress.innerText = alias.address
const tdCreated = document.createElement('td')
tdCreated.innerText = alias.created
const tdActions = document.createElement('td')
const aDelete = document.createElement('a')
.map(alias => ({ ...alias, created: new Date(alias.created).toLocaleString() }))
});
aDelete.addEventListener('click', async () => {
const res = await fetch(`/aliases/${alias.id}`, {
dataContainer.innerHTML = html;
}
function createAlias() {
const tags = document.getElementById('tags').value?.split(',') || [];
fetch('/aliases', {
method: 'POST',
body: JSON.stringify({ tags }),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(data => {
renderAliases(data)
})
}
function deleteAlias(id) {
fetch(`/aliases/${id}`, {
method: 'DELETE'
})
if (res.ok) {
const data = await res.json()
.then(res => res.json())
.then(data => {
renderAliases(data)
return
}
})
aDelete.classList.add('btn', 'btn-danger')
aDelete.innerText = 'Delete'
tdActions.appendChild(aDelete)
tr.appendChild(tdAddress)
tr.appendChild(tdCreated)
tr.appendChild(tdActions)
tbody.appendChild(tr)
})
table.appendChild(tbody)
dataContainer.innerHTML = ''
dataContainer.appendChild(table)
renderCreateButton();
}
function renderCreateButton() {
const dataContainer = document.getElementById('container')
const button = document.createElement('a')
button.classList.add('btn')
button.classList.add('btn-primary')
button.addEventListener('click', async () => {
button.innerText = 'Creating, please wait...'
const res = await fetch('/aliases', {
method: 'POST'
})
if (res.ok) {
const data = await res.json()
renderAliases(data)
return
}
})
button.innerText = 'Create new alias'
dataContainer.appendChild(button)
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
}
const defaultTemplate = `
<table class="table">
<thead>
<tr>
<th scope="col">Address</th>
<th scope="col">Tags</th>
<th scope="col">Created</th>
<th scope="col">Action</th>
</tr>
</thead>
<tbody>
{{#each data}}
<tr>
<td onclick="copyToClipboard('{{address}}')" style="cursor: pointer;">{{address}}</td>
<td> {{#each tags}}{{this}} {{/each}}</td>
<td>{{created}}</td>
<td>
<div onclick="deleteAlias('{{id}}')" class="btn btn-danger {{#unless id}}disabled{{/unless}}">Delete</div>
</td>
</tr>
{{/each}}
</tbody>
</table>
<input type="text" id="tags" placeholder="Add tags (optional)">
<div class="btn btn-primary" onclick="createAlias()">Create alias</div class="btn btn-primary">
`
// Responsive card template for mobile
const responsiveCardTemplate = `
{{#each data}}
<div class="card">
<div class="card-body">
<h5 class="card-title
{{#if tags.length}}
text-primary
{{/if}}
">
{{#each tags}}
<span class="badge bg-secondary">{{this}}</span>
{{/each}}
</h5>
<h5 class="card-title" onclick="copyToClipboard('{{address}}')" style="cursor: pointer;">{{address}}</h5>
<p class="card-text">{{created}}</p>
<div onclick="deleteAlias('{{id}}')" class="btn btn-sm btn-danger {{#unless id}}disabled{{/unless}}">Delete</div>
</div>
</div>
{{/each}}
<input type="text" id="tags" placeholder="Add tags (optional)">
<button class="btn btn-primary btn-sm" onclick="createAlias()">Create alias</button">
`
fetch('/aliases').then(async res => {
const data = await res.json()
const dataContainer = document.getElementById('container')
const dataContainer = document.getElementById('container');
const data = await res.json();
if (!res.ok) {
dataContainer.innerHTML = `

BIN
public/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -1,18 +1,19 @@
# walias
# Wildflock
> Manage email aliases for Wild Duck mail server
Headless service for managing address aliases in [Wild Duck](https://wildduck.email/) mail server.
## About
This project was build for [K-Space Hackathon2023](https://wiki.k-space.ee/en/hackathon/2023)
This project was built for [K-Space Hackathon 2023](https://wiki.k-space.ee/en/hackathon/2023) and is supposed to live in K-Space infrastructure; however, it can be seamlessly deployed elsewhere.
The main motivation behind the development of this headless web UI was to provide users with a convenient tool for creating and deleting email aliases in the Wild Duck mail server.
## Features
* auth with oidc
* headless only, no DB
* develop/debug with skaffold on k8s cluster
The Wild Duck server has a nice and functional [API](https://docs.wildduck.email/api/), but only offers an admin API token, limiting regular users' ability to manage their own email aliases.
User authentication is implemented via OIDC.
No persistent storage, Redis is used for keeping express sessions.
Deployed on Kubernetes, developed with Skaffold.
![Screenshot](./public/screenshot.png)
## TODO
* remove TS-related hacks
* add tests
* make nicer UI
* implement persistent sessions for multi-docker deployment (redis, db, etc)

View File

@ -1,19 +1,22 @@
import { randomUUID } from 'crypto';
import { feathers } from '@feathersjs/feathers';
import express, { rest, json, urlencoded, cors, serveStatic, notFound, errorHandler } from '@feathersjs/express';
import configuration from '@feathersjs/configuration';
import socketio from '@feathersjs/socketio';
import session from 'express-session';
import cookieParser from 'cookie-parser';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
import config from 'config';
import type { Application } from './declarations';
import { logger } from './logger';
import { logError } from './hooks/log-error';
import { services } from './services/index';
import { channels } from './channels';
import { randomUUID } from 'crypto';
import { Env, getEnv } from './helpers/get-env';
const app: Application = express(feathers());
let sessionStore;
// Load app configuration
app.configure(configuration());
@ -25,11 +28,26 @@ app.use(
);
app.use(cookieParser());
if (getEnv() === Env.prod) {
const redisClient = createClient({
url: config.get('redis.url'),
});
sessionStore = new RedisStore({
prefix: 'walias:',
client: redisClient,
});
redisClient.connect().catch(console.error);
}
app.use(
session({
secret: randomUUID(),
store: sessionStore,
secret: config.get('sessionSecret') || randomUUID(),
resave: false,
saveUninitialized: true,
saveUninitialized: false,
cookie: { secure: false },
})
);

16
src/helpers/get-env.ts Normal file
View File

@ -0,0 +1,16 @@
export enum Env {
dev = 'dev',
prod = 'prod',
test = 'test',
}
export const getEnv = (): Env => {
const env = process.env.NODE_ENV;
if (env === 'prod') {
return Env.prod;
} else if (env === 'test') {
return Env.test;
} else {
return Env.dev;
}
};

View File

@ -1,10 +1,7 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/logging.html
import { createLogger, format, transports } from 'winston';
// Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston
export const logger = createLogger({
// To see more detailed errors, change this to 'debug'
level: 'info',
format: format.combine(format.splat(), format.simple()),
format: format.combine(format.splat(), format.json()),
transports: [new transports.Console()],
});

View File

@ -6,7 +6,7 @@ import { faker } from '@faker-js/faker';
import { BadRequest } from '@feathersjs/errors';
import config from 'config';
interface Alias {
interface WildDuckAddress {
success: boolean;
id: string;
address: string;
@ -16,21 +16,27 @@ interface Alias {
created: string;
}
interface GetAddressInfoResponse {
interface GetWildDuckAddressInfoResponse {
success: boolean;
results: Alias[];
results: WildDuckAddress[];
}
interface CreateAddressResponse {
interface AliasApiResponse {
id: string | null;
address: string;
tags: string[];
created: string;
}
interface CreateWildDuckAddressResponse {
success: boolean;
id: string;
}
type AliasesData = any;
type AliasesData = { tags?: string[] }
type AliasesPatch = any;
type AliasesQuery = any;
export type { Alias as Aliases, AliasesData, AliasesPatch, AliasesQuery };
export type { WildDuckAddress as Aliases, AliasesData, AliasesPatch, AliasesQuery };
export interface AliasesServiceOptions {
app: Application;
@ -41,18 +47,18 @@ export interface AliasesParams extends Params<AliasesQuery> {
}
export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
implements ServiceInterface<Alias, AliasesData, ServiceParams, AliasesPatch>
implements ServiceInterface<AliasApiResponse, AliasesData, ServiceParams, AliasesPatch>
{
constructor(public options: AliasesServiceOptions) {}
constructor(public options: AliasesServiceOptions) { }
async find(params: ServiceParams): Promise<Alias[]> {
async find(params: ServiceParams): Promise<AliasApiResponse[]> {
const userId = await this.getUserIdByEmailAddress(params);
return this.getUserAddresses(userId);
}
async create(data: AliasesData, params: ServiceParams): Promise<Alias>;
async create(data: AliasesData, params: ServiceParams): Promise<Alias | Alias[]> {
async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse>;
async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse | AliasApiResponse[]> {
const userId = await this.getUserIdByEmailAddress(params);
const randomString = faker.git.commitSha({ length: 4 });
@ -65,8 +71,9 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
const emailDomain = config.get('wildDuck.domain');
const createResult = await wildDuckClient.post<CreateAddressResponse>(`/users/${userId}/addresses`, {
const createResult = await wildDuckClient.post<CreateWildDuckAddressResponse>(`/users/${userId}/addresses`, {
address: `${alias}@${emailDomain}`,
tags: data.tags,
});
if (!createResult.data.success) {
@ -79,25 +86,31 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
private async getUserIdByEmailAddress(params: ServiceParams): Promise<string> {
const emails = params.session?.user?.emails;
const preferredDomain = config.get('wildDuck.preferredDomain');
if (!emails.length || !preferredDomain) {
throw new BadRequest('Unable to find user');
}
const addressInfoResponse = await Promise.any(
emails
.filter((email: string) => email.endsWith(config.get('wildDuck.preferredDomain')))
.map((email: string) => wildDuckClient.get<Alias>(`addresses/resolve/${email}`))
.map((email: string) => wildDuckClient.get<WildDuckAddress>(`addresses/resolve/${email}`))
);
return addressInfoResponse.data.user;
}
private async getUserAddresses(userId: string): Promise<Alias[]> {
const { data: userAddressesResponse } = await wildDuckClient.get<GetAddressInfoResponse>(
private async getUserAddresses(userId: string): Promise<AliasApiResponse[]> {
const { data: userAddressesResponse } = await wildDuckClient.get<GetWildDuckAddressInfoResponse>(
`/users/${userId}/addresses`
);
return userAddressesResponse.results;
return userAddressesResponse.results.map(this.sanitizeAliasResponse);
}
async remove(id: NullableId, params: ServiceParams): Promise<Alias[]> {
const { data: addressInfoResponse } = await wildDuckClient.get<Alias>(`addresses/resolve/${id}`);
async remove(id: NullableId, params: ServiceParams): Promise<AliasApiResponse[]> {
const { data: addressInfoResponse } = await wildDuckClient.get<WildDuckAddress>(`addresses/resolve/${id}`);
const allowedDomain: string = config.get('wildDuck.domain');
// If address does not match the allowed domain, throw an error
@ -106,10 +119,23 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
}
const userId = await this.getUserIdByEmailAddress(params);
await wildDuckClient.delete<Alias>(`users/${userId}/addresses/${id}`);
await wildDuckClient.delete<WildDuckAddress>(`users/${userId}/addresses/${id}`);
return this.getUserAddresses(userId);
}
sanitizeAliasResponse(alias: WildDuckAddress): AliasApiResponse {
// Prevent the user from deleting their main address or any address that does not end with the preferred domain
const isRemovable = !alias.main && alias.address.endsWith(config.get('wildDuck.domain'));
// Hide the id if the alias is not removable
return {
id: isRemovable ? alias.id : null,
address: alias.address,
tags: alias.tags,
created: alias.created,
};
}
}
export const getOptions = (app: Application) => {

View File

@ -29,17 +29,19 @@ export class AuthOidcService<ServiceParams extends AuthOidcParams = AuthOidcPara
client_id: config.get('oidc.clientId'),
client_secret: config.get('oidc.clientSecret'),
redirect_uris: [config.get('oidc.redirectUris')],
response_types: ['code'],
response_types: [config.get('oidc.responseTypes')],
id_token_signed_response_alg: config.get('oidc.signedResponseAlg'),
token_endpoint_auth_method: config.get('oidc.authMethod'),
});
const codeVerifier = generators.codeVerifier();
const codeChallenge = generators.codeChallenge(codeVerifier);
const url = client.authorizationUrl({
redirect_uri: config.get('clientUrl') + '/auth-oidc/callback',
scope: 'openid profile offline_access',
response_type: 'code',
scope: config.get('oidc.scopes'),
response_type: config.get('oidc.responseTypes'),
code_challenge: codeChallenge,
code_challenge_method: 'S256',
code_challenge_method: config.get('oidc.codeChallengeMethod'),
});
params.session.codeVerifier = codeVerifier;