store express sessions in redis, delete only aliases from preferred domain #3

Merged
lauri merged 5 commits from dev into master 2023-08-12 08:14:49 +00:00
11 changed files with 337 additions and 51 deletions

View File

@ -1,9 +1,9 @@
{ {
"trailingComma": "es5", "trailingComma": "es5",
"tabWidth": 4, "tabWidth": 4,
"semi": true, "semi": true,
"singleQuote": true, "singleQuote": true,
"printWidth": 120, "printWidth": 120,
"quoteProps": "as-needed", "quoteProps": "as-needed",
"arrowParens": "avoid" "arrowParens": "avoid"
} }

View File

@ -14,5 +14,8 @@
"token": "aaaaa", "token": "aaaaa",
"domain": "test-codemowers.eu", "domain": "test-codemowers.eu",
"preferredDomain": "k-space.ee" "preferredDomain": "k-space.ee"
},
"redis": {
"url": "redis://localhost:6379"
} }
} }

View File

@ -10,5 +10,8 @@ module.exports = {
url: process.env.WILDDUCK_URL, url: process.env.WILDDUCK_URL,
token: process.env.WILDDUCK_TOKEN, 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,8 @@ spec:
secretKeyRef: secretKeyRef:
name: walias-secrets name: walias-secrets
key: WILDDUCK_DOMAIN key: WILDDUCK_DOMAIN
- name: REDIS_URL
value: walias-cache
envFrom: envFrom:
- secretRef: - secretRef:
name: oidc-client-walias-owner-secrets name: oidc-client-walias-owner-secrets

205
package-lock.json generated
View File

@ -22,8 +22,10 @@
"axios": "^1.4.0", "axios": "^1.4.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"config": "^3.3.9", "config": "^3.3.9",
"connect-redis": "^7.1.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"ioredis": "^5.3.2",
"openid-client": "^5.4.3", "openid-client": "^5.4.3",
"winston": "^3.10.0" "winston": "^3.10.0"
}, },
@ -34,6 +36,7 @@
"@types/express-session": "^1.17.7", "@types/express-session": "^1.17.7",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node": "^20.4.5", "@types/node": "^20.4.5",
"@types/redis": "^4.0.11",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"husky": "^8.0.3", "husky": "^8.0.3",
"lint-staged": "^13.2.3", "lint-staged": "^13.2.3",
@ -413,6 +416,11 @@
"url": "https://github.com/sponsors/daffl" "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": { "node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
@ -438,6 +446,65 @@
"@jridgewell/sourcemap-codec": "^1.4.10" "@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==",
"dev": true,
"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==",
"dev": true,
"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==",
"dev": true,
"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==",
"dev": true,
"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==",
"dev": true,
"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==",
"dev": true,
"peerDependencies": {
"@redis/client": "^1.0.0"
}
},
"node_modules/@socket.io/component-emitter": { "node_modules/@socket.io/component-emitter": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
@ -610,6 +677,16 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz",
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==" "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": { "node_modules/@types/send": {
"version": "0.17.1", "version": "0.17.1",
"resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz", "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.1.tgz",
@ -1229,6 +1306,14 @@
"node": ">=0.8" "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": { "node_modules/color": {
"version": "3.2.1", "version": "3.2.1",
"resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz",
@ -1340,6 +1425,17 @@
"node": ">= 10.0.0" "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": { "node_modules/content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@ -1501,6 +1597,14 @@
"node": ">=0.4.0" "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": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@ -1999,6 +2103,15 @@
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" "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==",
"dev": true,
"engines": {
"node": ">= 4"
}
},
"node_modules/get-caller-file": { "node_modules/get-caller-file": {
"version": "2.0.5", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
@ -2274,6 +2387,50 @@
"node": ">= 0.10" "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": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@ -2650,6 +2807,16 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "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": { "node_modules/log-symbols": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
@ -3536,6 +3703,39 @@
"node": ">= 0.10" "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==",
"dev": true,
"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": { "node_modules/regenerator-runtime": {
"version": "0.13.11", "version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -3920,6 +4120,11 @@
"node": "*" "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": { "node_modules/statuses": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz",

View File

@ -55,8 +55,10 @@
"axios": "^1.4.0", "axios": "^1.4.0",
"compression": "^1.7.4", "compression": "^1.7.4",
"config": "^3.3.9", "config": "^3.3.9",
"connect-redis": "^7.1.0",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
"express-session": "^1.17.3", "express-session": "^1.17.3",
"ioredis": "^5.3.2",
"openid-client": "^5.4.3", "openid-client": "^5.4.3",
"winston": "^3.10.0" "winston": "^3.10.0"
}, },
@ -67,6 +69,7 @@
"@types/express-session": "^1.17.7", "@types/express-session": "^1.17.7",
"@types/mocha": "^10.0.1", "@types/mocha": "^10.0.1",
"@types/node": "^20.4.5", "@types/node": "^20.4.5",
"@types/redis": "^4.0.11",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"husky": "^8.0.3", "husky": "^8.0.3",
"lint-staged": "^13.2.3", "lint-staged": "^13.2.3",

View File

@ -31,29 +31,17 @@ function renderAliases(aliases) {
const tdCreated = document.createElement('td') const tdCreated = document.createElement('td')
tdCreated.innerText = alias.created tdCreated.innerText = alias.created
const tdActions = document.createElement('td') const tdActions = document.createElement('td')
const aDelete = document.createElement('a')
aDelete.addEventListener('click', async () => { const deleteButton = createDeleteButton(alias)
const res = await fetch(`/aliases/${alias.id}`, {
method: 'DELETE'
})
if (res.ok) { tdActions.appendChild(deleteButton)
const data = await res.json()
renderAliases(data)
return
}
})
aDelete.classList.add('btn', 'btn-danger')
aDelete.innerText = 'Delete'
tdActions.appendChild(aDelete)
tr.appendChild(tdAddress) tr.appendChild(tdAddress)
tr.appendChild(tdCreated) tr.appendChild(tdCreated)
tr.appendChild(tdActions) tr.appendChild(tdActions)
tbody.appendChild(tr) tbody.appendChild(tr)
}) })
table.appendChild(tbody) table.appendChild(tbody)
dataContainer.innerHTML = '' dataContainer.innerHTML = ''
@ -62,6 +50,32 @@ function renderAliases(aliases) {
renderCreateButton(); renderCreateButton();
} }
function createDeleteButton(alias) {
const deleteButton = document.createElement('a')
deleteButton.classList.add('btn', 'btn-danger')
if (!alias.id) {
deleteButton.classList.add('disabled')
} else {
deleteButton.addEventListener('click', async () => {
const res = await fetch(`/aliases/${alias.id}`, {
method: 'DELETE'
})
if (res.ok) {
const data = await res.json()
renderAliases(data)
return
}
})
}
deleteButton.innerText = 'Delete'
return deleteButton
}
function renderCreateButton() { function renderCreateButton() {
const dataContainer = document.getElementById('container') const dataContainer = document.getElementById('container')
const button = document.createElement('a') const button = document.createElement('a')

View File

@ -7,12 +7,13 @@
This project was build for [K-Space Hackathon2023](https://wiki.k-space.ee/en/hackathon/2023) This project was build for [K-Space Hackathon2023](https://wiki.k-space.ee/en/hackathon/2023)
## Features ## Features
* auth with oidc
* headless only, no DB - auth with oidc
* develop/debug with skaffold on k8s cluster - headless only, no DB
- develop/debug with skaffold on k8s cluster
## TODO ## TODO
* remove TS-related hacks
* add tests - remove TS-related hacks
* make nicer UI - add tests
* implement persistent sessions for multi-docker deployment (redis, db, etc) - make nicer UI

View File

@ -1,17 +1,19 @@
import { randomUUID } from 'crypto';
import { feathers } from '@feathersjs/feathers'; import { feathers } from '@feathersjs/feathers';
import express, { rest, json, urlencoded, cors, serveStatic, notFound, errorHandler } from '@feathersjs/express'; import express, { rest, json, urlencoded, cors, serveStatic, notFound, errorHandler } from '@feathersjs/express';
import configuration from '@feathersjs/configuration'; import configuration from '@feathersjs/configuration';
import socketio from '@feathersjs/socketio'; import socketio from '@feathersjs/socketio';
import session from 'express-session'; import session from 'express-session';
import cookieParser from 'cookie-parser'; import cookieParser from 'cookie-parser';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
import config from 'config';
import type { Application } from './declarations'; import type { Application } from './declarations';
import { logger } from './logger'; import { logger } from './logger';
import { logError } from './hooks/log-error'; import { logError } from './hooks/log-error';
import { services } from './services/index'; import { services } from './services/index';
import { channels } from './channels'; import { channels } from './channels';
import { randomUUID } from 'crypto'; import { Env, getEnv } from './helpers/get-env';
const app: Application = express(feathers()); const app: Application = express(feathers());
@ -25,11 +27,23 @@ app.use(
); );
app.use(cookieParser()); app.use(cookieParser());
const sessionStore =
getEnv() === Env.prod
? new RedisStore({
prefix: 'walias:',
client: createClient({
url: config.get('redis.url'),
}),
})
: undefined;
app.use( app.use(
session({ session({
store: sessionStore,
secret: randomUUID(), secret: randomUUID(),
resave: false, resave: false,
saveUninitialized: true, saveUninitialized: false,
cookie: { secure: 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

@ -2,11 +2,11 @@ import type { NullableId, Params, ServiceInterface } from '@feathersjs/feathers'
import type { Application } from '../../declarations'; import type { Application } from '../../declarations';
import wildDuckClient from '../../clients/wildduck.client'; import wildDuckClient from '../../clients/wildduck.client';
import { faker } from '@faker-js/faker'; import { faker, th } from '@faker-js/faker';
import { BadRequest } from '@feathersjs/errors'; import { BadRequest } from '@feathersjs/errors';
import config from 'config'; import config from 'config';
interface Alias { interface WildDuckAddress {
success: boolean; success: boolean;
id: string; id: string;
address: string; address: string;
@ -16,12 +16,19 @@ interface Alias {
created: string; created: string;
} }
interface GetAddressInfoResponse { interface GetWildDuckAddressInfoResponse {
success: boolean; success: boolean;
results: Alias[]; results: WildDuckAddress[];
} }
interface CreateAddressResponse { interface AliasApiResponse {
id: string | null;
address: string;
tags: string[];
created: string;
}
interface CreateWildDuckAddressResponse {
success: boolean; success: boolean;
id: string; id: string;
} }
@ -30,7 +37,7 @@ type AliasesData = any;
type AliasesPatch = any; type AliasesPatch = any;
type AliasesQuery = any; type AliasesQuery = any;
export type { Alias as Aliases, AliasesData, AliasesPatch, AliasesQuery }; export type { WildDuckAddress as Aliases, AliasesData, AliasesPatch, AliasesQuery };
export interface AliasesServiceOptions { export interface AliasesServiceOptions {
app: Application; app: Application;
@ -41,18 +48,18 @@ export interface AliasesParams extends Params<AliasesQuery> {
} }
export class AliasesService<ServiceParams extends AliasesParams = AliasesParams> 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); const userId = await this.getUserIdByEmailAddress(params);
return this.getUserAddresses(userId); return this.getUserAddresses(userId);
} }
async create(data: AliasesData, params: ServiceParams): Promise<Alias>; async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse>;
async create(data: AliasesData, params: ServiceParams): Promise<Alias | Alias[]> { async create(data: AliasesData, params: ServiceParams): Promise<AliasApiResponse | AliasApiResponse[]> {
const userId = await this.getUserIdByEmailAddress(params); const userId = await this.getUserIdByEmailAddress(params);
const randomString = faker.git.commitSha({ length: 4 }); const randomString = faker.git.commitSha({ length: 4 });
@ -65,7 +72,7 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
const emailDomain = config.get('wildDuck.domain'); 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}`, address: `${alias}@${emailDomain}`,
}); });
@ -79,25 +86,31 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
private async getUserIdByEmailAddress(params: ServiceParams): Promise<string> { private async getUserIdByEmailAddress(params: ServiceParams): Promise<string> {
const emails = params.session?.user?.emails; 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( const addressInfoResponse = await Promise.any(
emails emails
.filter((email: string) => email.endsWith(config.get('wildDuck.preferredDomain'))) .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; return addressInfoResponse.data.user;
} }
private async getUserAddresses(userId: string): Promise<Alias[]> { private async getUserAddresses(userId: string): Promise<AliasApiResponse[]> {
const { data: userAddressesResponse } = await wildDuckClient.get<GetAddressInfoResponse>( const { data: userAddressesResponse } = await wildDuckClient.get<GetWildDuckAddressInfoResponse>(
`/users/${userId}/addresses` `/users/${userId}/addresses`
); );
return userAddressesResponse.results; return userAddressesResponse.results.map(this.sanitizeAliasResponse);
} }
async remove(id: NullableId, params: ServiceParams): Promise<Alias[]> { async remove(id: NullableId, params: ServiceParams): Promise<AliasApiResponse[]> {
const { data: addressInfoResponse } = await wildDuckClient.get<Alias>(`addresses/resolve/${id}`); const { data: addressInfoResponse } = await wildDuckClient.get<WildDuckAddress>(`addresses/resolve/${id}`);
const allowedDomain: string = config.get('wildDuck.domain'); const allowedDomain: string = config.get('wildDuck.domain');
// If address does not match the allowed domain, throw an error // If address does not match the allowed domain, throw an error
@ -106,10 +119,22 @@ export class AliasesService<ServiceParams extends AliasesParams = AliasesParams>
} }
const userId = await this.getUserIdByEmailAddress(params); 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); return this.getUserAddresses(userId);
} }
sanitizeAliasResponse(alias: WildDuckAddress): AliasApiResponse {
// Hide the id if the alias is not removable
const isRemovable = alias.main || !alias.address.endsWith(config.get('wildDuck.preferredDomain'));
return {
id: isRemovable ? null : alias.id,
address: alias.address,
tags: alias.tags,
created: alias.created,
};
}
} }
export const getOptions = (app: Application) => { export const getOptions = (app: Application) => {