25 Commits
dev ... master

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
19 changed files with 289 additions and 260 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

View File

@@ -1,10 +1,20 @@
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,
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,
@@ -13,6 +23,5 @@ module.exports = {
},
redis: {
url: process.env.REDIS_URL,
password: process.env.REDIS_PASSWORD,
},
};

View File

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

145
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",
@@ -26,7 +26,8 @@
"cookie-parser": "^1.4.6",
"express-session": "^1.17.3",
"ioredis": "^5.3.2",
"openid-client": "^5.4.3",
"openid-client": "^5.6.5",
"redis": "^4.6.7",
"winston": "^3.10.0"
},
"devDependencies": {
@@ -450,7 +451,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
"dev": true,
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -459,7 +459,6 @@
"version": "1.5.8",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.5.8.tgz",
"integrity": "sha512-xzElwHIO6rBAqzPeVnCzgvrnBEcFL1P0w8P65VNLRkdVW8rOE58f52hdj0BDgmsdOm4f1EoXPZtH4Fh7M/qUpw==",
"dev": true,
"dependencies": {
"cluster-key-slot": "1.1.2",
"generic-pool": "3.9.0",
@@ -473,7 +472,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.0.tgz",
"integrity": "sha512-16yZWngxyXPd+MJxeSr0dqh2AIOi8j9yXKcKCwVaKDbH3HTuETpDVPcLujhFYVPtYrngSco31BUcSa9TH31Gqg==",
"dev": true,
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -482,7 +480,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.4.tgz",
"integrity": "sha512-LUZE2Gdrhg0Rx7AN+cZkb1e6HjoSKaeeW8rYnt89Tly13GBI5eP4CwDVr+MY8BAYfCg4/N15OUrtLoona9uSgw==",
"dev": true,
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -491,7 +488,6 @@
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.1.3.tgz",
"integrity": "sha512-4Dg1JjvCevdiCBTZqjhKkGoC5/BcB7k9j99kdMnaXFXg8x4eyOIVg9487CMv7/BUVkFLZCaIh8ead9mU15DNng==",
"dev": true,
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -500,7 +496,6 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.0.4.tgz",
"integrity": "sha512-ThUIgo2U/g7cCuZavucQTQzA9g9JbDDY2f64u3AbAoz/8vE2lt2U37LamDUVChhaDA3IRT9R6VvJwqnUfTJzng==",
"dev": true,
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -944,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"
}
@@ -1008,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",
@@ -1021,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"
},
@@ -1048,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"
@@ -1475,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"
}
@@ -1670,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",
@@ -1682,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"
}
@@ -1828,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",
@@ -1972,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"
@@ -2031,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",
@@ -2107,7 +2102,6 @@
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
"dev": true,
"engines": {
"node": ">= 4"
}
@@ -2555,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"
}
@@ -3399,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"
@@ -3645,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",
@@ -3707,7 +3701,6 @@
"version": "4.6.7",
"resolved": "https://registry.npmjs.org/redis/-/redis-4.6.7.tgz",
"integrity": "sha512-KrkuNJNpCwRm5vFJh0tteMxW8SaUzkm5fBH7eL5hd/D0fAkzvapxbfGPP/r+4JAXdQuX7nebsBkBqA2RHB7Usw==",
"dev": true,
"dependencies": {
"@redis/bloom": "1.2.0",
"@redis/client": "1.5.8",
@@ -4051,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",
@@ -4562,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": {
@@ -59,7 +66,8 @@
"cookie-parser": "^1.4.6",
"express-session": "^1.17.3",
"ioredis": "^5.3.2",
"openid-client": "^5.4.3",
"openid-client": "^5.6.5",
"redis": "^4.6.7",
"winston": "^3.10.0"
},
"devDependencies": {

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;
@@ -54,7 +55,7 @@
<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,107 +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')
.map(alias => ({ ...alias, created: new Date(alias.created).toLocaleString() }))
});
const deleteButton = createDeleteButton(alias)
tdActions.appendChild(deleteButton)
tr.appendChild(tdAddress)
tr.appendChild(tdCreated)
tr.appendChild(tdActions)
tbody.appendChild(tr)
})
table.appendChild(tbody)
dataContainer.innerHTML = ''
dataContainer.appendChild(table)
renderCreateButton();
dataContainer.innerHTML = html;
}
function createDeleteButton(alias) {
const deleteButton = document.createElement('a')
deleteButton.classList.add('btn', 'btn-danger')
function createAlias() {
const tags = document.getElementById('tags').value?.split(',') || [];
if (!alias.id) {
deleteButton.classList.add('disabled')
} else {
fetch('/aliases', {
method: 'POST',
body: JSON.stringify({ tags }),
headers: {
'Content-Type': 'application/json'
}
})
.then(res => res.json())
.then(data => {
renderAliases(data)
})
}
deleteButton.addEventListener('click', async () => {
const res = await fetch(`/aliases/${alias.id}`, {
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
}
})
}
deleteButton.innerText = 'Delete'
return deleteButton
function copyToClipboard(text) {
navigator.clipboard.writeText(text)
}
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)
}
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,19 +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
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.
- auth with oidc
- headless only, no DB
- develop/debug with skaffold on k8s cluster
User authentication is implemented via OIDC.
## TODO
No persistent storage, Redis is used for keeping express sessions.
Deployed on Kubernetes, developed with Skaffold.
![Screenshot](./public/screenshot.png)
- remove TS-related hacks
- add tests
- make nicer UI

View File

@@ -32,7 +32,6 @@ app.use(cookieParser());
if (getEnv() === Env.prod) {
const redisClient = createClient({
url: config.get('redis.url'),
password: config.get('redis.password'),
});
sessionStore = new RedisStore({
@@ -46,7 +45,7 @@ if (getEnv() === Env.prod) {
app.use(
session({
store: sessionStore,
secret: randomUUID(),
secret: config.get('sessionSecret') || randomUUID(),
resave: false,
saveUninitialized: false,
cookie: { secure: false },

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

@@ -2,7 +2,7 @@ import type { NullableId, Params, ServiceInterface } from '@feathersjs/feathers'
import type { Application } from '../../declarations';
import wildDuckClient from '../../clients/wildduck.client';
import { faker, th } from '@faker-js/faker';
import { faker } from '@faker-js/faker';
import { BadRequest } from '@feathersjs/errors';
import config from 'config';
@@ -32,8 +32,7 @@ interface CreateWildDuckAddressResponse {
success: boolean;
id: string;
}
type AliasesData = any;
type AliasesData = { tags?: string[] }
type AliasesPatch = any;
type AliasesQuery = any;
@@ -74,6 +73,7 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
const createResult = await wildDuckClient.post<CreateWildDuckAddressResponse>(`/users/${userId}/addresses`, {
address: `${alias}@${emailDomain}`,
tags: data.tags,
});
if (!createResult.data.success) {
@@ -125,11 +125,12 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
}
sanitizeAliasResponse(alias: WildDuckAddress): AliasApiResponse {
// Hide the id if the alias is not removable
const isRemovable = alias.main || !alias.address.endsWith(config.get('wildDuck.preferredDomain'));
// 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 ? null : alias.id,
id: isRemovable ? alias.id : null,
address: alias.address,
tags: alias.tags,
created: alias.created,

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;