Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
|
f00cf653c0 |
|
@ -13,7 +13,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v4
|
uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Install Docker
|
- name: Install Docker
|
||||||
run: |-
|
run: |-
|
||||||
|
@ -28,7 +28,7 @@ jobs:
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: actions/docker-metadata-action@v5
|
uses: actions/docker-metadata-action@v4
|
||||||
with:
|
with:
|
||||||
# list of Docker images to use as base name for tags
|
# list of Docker images to use as base name for tags
|
||||||
images: git.amogus.cloud/lea/maddy-admin
|
images: git.amogus.cloud/lea/maddy-admin
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"dayjs": "^1.11.10",
|
"dayjs": "^1.11.10",
|
||||||
"lucide-react": "^0.311.0",
|
"lucide-react": "^0.311.0",
|
||||||
"next": "14.0.4",
|
"next": "14.0.4",
|
||||||
"next-auth": "^4.24.5",
|
"next-auth": "5.0.0-beta.18",
|
||||||
"next-swagger-doc": "^0.4.0",
|
"next-swagger-doc": "^0.4.0",
|
||||||
"random-words": "^2.0.0",
|
"random-words": "^2.0.0",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
|
104
pnpm-lock.yaml
104
pnpm-lock.yaml
|
@ -27,8 +27,8 @@ dependencies:
|
||||||
specifier: 14.0.4
|
specifier: 14.0.4
|
||||||
version: 14.0.4(react-dom@18.2.0)(react@18.2.0)
|
version: 14.0.4(react-dom@18.2.0)(react@18.2.0)
|
||||||
next-auth:
|
next-auth:
|
||||||
specifier: ^4.24.5
|
specifier: 5.0.0-beta.18
|
||||||
version: 4.24.5(next@14.0.4)(react-dom@18.2.0)(react@18.2.0)
|
version: 5.0.0-beta.18(next@14.0.4)(react@18.2.0)
|
||||||
next-swagger-doc:
|
next-swagger-doc:
|
||||||
specifier: ^0.4.0
|
specifier: ^0.4.0
|
||||||
version: 0.4.0(next@14.0.4)(openapi-types@12.1.3)
|
version: 0.4.0(next@14.0.4)(openapi-types@12.1.3)
|
||||||
|
@ -124,6 +124,29 @@ packages:
|
||||||
z-schema: 5.0.5
|
z-schema: 5.0.5
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@auth/core@0.31.0:
|
||||||
|
resolution: {integrity: sha512-UKk3psvA1cRbk4/c9CkpWB8mdWrkKvzw0DmEYRsWolUQytQ2cRqx+hYuV6ZCsngw/xbj9hpmkZmAZEyq2g4fMg==}
|
||||||
|
peerDependencies:
|
||||||
|
'@simplewebauthn/browser': ^9.0.1
|
||||||
|
'@simplewebauthn/server': ^9.0.2
|
||||||
|
nodemailer: ^6.8.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@simplewebauthn/browser':
|
||||||
|
optional: true
|
||||||
|
'@simplewebauthn/server':
|
||||||
|
optional: true
|
||||||
|
nodemailer:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@panva/hkdf': 1.1.1
|
||||||
|
'@types/cookie': 0.6.0
|
||||||
|
cookie: 0.6.0
|
||||||
|
jose: 5.3.0
|
||||||
|
oauth4webapi: 2.10.4
|
||||||
|
preact: 10.11.3
|
||||||
|
preact-render-to-string: 5.2.3(preact@10.11.3)
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@babel/runtime-corejs3@7.23.8:
|
/@babel/runtime-corejs3@7.23.8:
|
||||||
resolution: {integrity: sha512-2ZzmcDugdm0/YQKFVYsXiwUN7USPX8PM7cytpb4PFl87fM+qYPSvTZX//8tyeJB1j0YDmafBJEbl5f8NfLyuKw==}
|
resolution: {integrity: sha512-2ZzmcDugdm0/YQKFVYsXiwUN7USPX8PM7cytpb4PFl87fM+qYPSvTZX//8tyeJB1j0YDmafBJEbl5f8NfLyuKw==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
@ -1976,6 +1999,10 @@ packages:
|
||||||
resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==}
|
resolution: {integrity: sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@types/cookie@0.6.0:
|
||||||
|
resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@types/hast@2.3.9:
|
/@types/hast@2.3.9:
|
||||||
resolution: {integrity: sha512-pTHyNlaMD/oKJmS+ZZUyFUcsZeBZpC0lmGquw98CqRVNgAdJZJeD7GoeLiT6Xbx5rU9VCjSt0RwEvDgzh4obFw==}
|
resolution: {integrity: sha512-pTHyNlaMD/oKJmS+ZZUyFUcsZeBZpC0lmGquw98CqRVNgAdJZJeD7GoeLiT6Xbx5rU9VCjSt0RwEvDgzh4obFw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -2634,11 +2661,6 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/cookie@0.5.0:
|
|
||||||
resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==}
|
|
||||||
engines: {node: '>= 0.6'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/cookie@0.6.0:
|
/cookie@0.6.0:
|
||||||
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==}
|
||||||
engines: {node: '>= 0.6'}
|
engines: {node: '>= 0.6'}
|
||||||
|
@ -3973,8 +3995,8 @@ packages:
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/jose@4.15.4:
|
/jose@5.3.0:
|
||||||
resolution: {integrity: sha512-W+oqK4H+r5sITxfxpSU+MMdr/YSWGvgZMQDIsNoBDGGy4i7GBPTtvFKibQzW06n3U3TqHjhvBJsirShsEJ6eeQ==}
|
resolution: {integrity: sha512-IChe9AtAE79ru084ow8jzkN2lNrG3Ntfiv65Cvj9uOCE2m5LNsdHG+9EbxWxAoWRF9TgDOqLN5jm08++owDVRg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/js-file-download@0.4.12:
|
/js-file-download@0.4.12:
|
||||||
|
@ -4358,29 +4380,25 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/next-auth@4.24.5(next@14.0.4)(react-dom@18.2.0)(react@18.2.0):
|
/next-auth@5.0.0-beta.18(next@14.0.4)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-3RafV3XbfIKk6rF6GlLE4/KxjTcuMCifqrmD+98ejFq73SRoj2rmzoca8u764977lH/Q7jo6Xu6yM+Re1Mz/Og==}
|
resolution: {integrity: sha512-x55L8wZb8PcPGCYA3e/l9tdpd7YL3FDuhas4W8pxq3PjrWJ9OoDxNN0otK9axJamJBbBgjfzTJjVQB6hXoe0ZQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: ^12.2.5 || ^13 || ^14
|
'@simplewebauthn/browser': ^9.0.1
|
||||||
|
'@simplewebauthn/server': ^9.0.2
|
||||||
|
next: ^14
|
||||||
nodemailer: ^6.6.5
|
nodemailer: ^6.6.5
|
||||||
react: ^17.0.2 || ^18
|
react: ^18.2.0
|
||||||
react-dom: ^17.0.2 || ^18
|
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
|
'@simplewebauthn/browser':
|
||||||
|
optional: true
|
||||||
|
'@simplewebauthn/server':
|
||||||
|
optional: true
|
||||||
nodemailer:
|
nodemailer:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/runtime': 7.23.8
|
'@auth/core': 0.31.0
|
||||||
'@panva/hkdf': 1.1.1
|
|
||||||
cookie: 0.5.0
|
|
||||||
jose: 4.15.4
|
|
||||||
next: 14.0.4(react-dom@18.2.0)(react@18.2.0)
|
next: 14.0.4(react-dom@18.2.0)(react@18.2.0)
|
||||||
oauth: 0.9.15
|
|
||||||
openid-client: 5.6.4
|
|
||||||
preact: 10.19.3
|
|
||||||
preact-render-to-string: 5.2.6(preact@10.19.3)
|
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
uuid: 8.3.2
|
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/next-swagger-doc@0.4.0(next@14.0.4)(openapi-types@12.1.3):
|
/next-swagger-doc@0.4.0(next@14.0.4)(openapi-types@12.1.3):
|
||||||
|
@ -4525,19 +4543,14 @@ packages:
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/oauth@0.9.15:
|
/oauth4webapi@2.10.4:
|
||||||
resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==}
|
resolution: {integrity: sha512-DSoj8QoChzOCQlJkRmYxAJCIpnXFW32R0Uq7avyghIeB6iJq0XAblOD7pcq3mx4WEBDwMuKr0Y1qveCBleG2Xw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/object-assign@4.1.1:
|
/object-assign@4.1.1:
|
||||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
/object-hash@2.2.0:
|
|
||||||
resolution: {integrity: sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==}
|
|
||||||
engines: {node: '>= 6'}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/object-hash@3.0.0:
|
/object-hash@3.0.0:
|
||||||
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
|
@ -4603,11 +4616,6 @@ packages:
|
||||||
es-abstract: 1.22.3
|
es-abstract: 1.22.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/oidc-token-hash@5.0.3:
|
|
||||||
resolution: {integrity: sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==}
|
|
||||||
engines: {node: ^10.13.0 || >=12.0.0}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/once@1.4.0:
|
/once@1.4.0:
|
||||||
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -4625,15 +4633,6 @@ packages:
|
||||||
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
|
resolution: {integrity: sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/openid-client@5.6.4:
|
|
||||||
resolution: {integrity: sha512-T1h3B10BRPKfcObdBklX639tVz+xh34O7GjofqrqiAQdm7eHsQ00ih18x6wuJ/E6FxdtS2u3FmUGPDeEcMwzNA==}
|
|
||||||
dependencies:
|
|
||||||
jose: 4.15.4
|
|
||||||
lru-cache: 6.0.0
|
|
||||||
object-hash: 2.2.0
|
|
||||||
oidc-token-hash: 5.0.3
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/optionator@0.9.3:
|
/optionator@0.9.3:
|
||||||
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
|
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
|
||||||
engines: {node: '>= 0.8.0'}
|
engines: {node: '>= 0.8.0'}
|
||||||
|
@ -4840,17 +4839,17 @@ packages:
|
||||||
source-map-js: 1.0.2
|
source-map-js: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/preact-render-to-string@5.2.6(preact@10.19.3):
|
/preact-render-to-string@5.2.3(preact@10.11.3):
|
||||||
resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==}
|
resolution: {integrity: sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
preact: '>=10'
|
preact: '>=10'
|
||||||
dependencies:
|
dependencies:
|
||||||
preact: 10.19.3
|
preact: 10.11.3
|
||||||
pretty-format: 3.8.0
|
pretty-format: 3.8.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/preact@10.19.3:
|
/preact@10.11.3:
|
||||||
resolution: {integrity: sha512-nHHTeFVBTHRGxJXKkKu5hT8C/YWBkPso4/Gad6xuj5dbptt9iF9NZr9pHbPhBrnT2klheu7mHTxTZ/LjwJiEiQ==}
|
resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/prebuild-install@7.1.1:
|
/prebuild-install@7.1.1:
|
||||||
|
@ -6117,11 +6116,6 @@ packages:
|
||||||
/util-deprecate@1.0.2:
|
/util-deprecate@1.0.2:
|
||||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||||
|
|
||||||
/uuid@8.3.2:
|
|
||||||
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
|
|
||||||
hasBin: true
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/validator@13.11.0:
|
/validator@13.11.0:
|
||||||
resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==}
|
resolution: {integrity: sha512-Ii+sehpSfZy+At5nPdnyMhx78fEoPDkR2XW/zimHEL3MyGJQOCQ7WeP20jPYRz7ZCpcKLB21NxuXHF3bxjStBQ==}
|
||||||
engines: {node: '>= 0.10'}
|
engines: {node: '>= 0.10'}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import { fetchAuditLog } from "@/lib/actions";
|
||||||
import { AuditLog, AuditLogAction } from "@/lib/audit";
|
import { AuditLog, AuditLogAction } from "@/lib/audit";
|
||||||
import GhostMessage from "@/lib/components/ui/GhostMessage";
|
import GhostMessage from "@/lib/components/ui/GhostMessage";
|
||||||
import LoadingSpinner from "@/lib/components/ui/LoadingSpinner";
|
import LoadingSpinner from "@/lib/components/ui/LoadingSpinner";
|
||||||
import { avatarUrl, GRAVATAR_DEFAULT } from "@/lib/constants";
|
import { GRAVATAR_DEFAULT } from "@/lib/constants";
|
||||||
import { sha256sum } from "@/lib/util";
|
import { sha256sum } from "@/lib/util";
|
||||||
import { Avatar, Card, Code, Flex, Grid, Heading, IconButton, Text } from "@radix-ui/themes";
|
import { Avatar, Card, Code, Flex, Grid, Heading, IconButton, Text } from "@radix-ui/themes";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
@ -98,7 +98,7 @@ export default function Audit() {
|
||||||
<Flex direction='row' gap='4' align='center'>
|
<Flex direction='row' gap='4' align='center'>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="2"
|
size="2"
|
||||||
src={avatarUrl(item.user ?? "")}
|
src={`https://gravatar.com/avatar/${sha256sum(item.user)}?d=${GRAVATAR_DEFAULT}`}
|
||||||
radius='full'
|
radius='full'
|
||||||
fallback={item.user?.slice(0, 1) || "@"}
|
fallback={item.user?.slice(0, 1) || "@"}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -5,7 +5,7 @@ import GhostMessage from "@/lib/components/ui/GhostMessage";
|
||||||
import LoadingSpinner from "@/lib/components/ui/LoadingSpinner";
|
import LoadingSpinner from "@/lib/components/ui/LoadingSpinner";
|
||||||
import CreateUserButton from "@/lib/components/ui/admin/CreateUserButton";
|
import CreateUserButton from "@/lib/components/ui/admin/CreateUserButton";
|
||||||
import ManageUserButton from "@/lib/components/ui/admin/ManageUserButton";
|
import ManageUserButton from "@/lib/components/ui/admin/ManageUserButton";
|
||||||
import { avatarUrl, GRAVATAR_DEFAULT } from "@/lib/constants";
|
import { GRAVATAR_DEFAULT } from "@/lib/constants";
|
||||||
import { isAdmin, sha256sum } from "@/lib/util";
|
import { isAdmin, sha256sum } from "@/lib/util";
|
||||||
import { Avatar, Badge, Button, Card, Flex, Heading, Table, Text, TextField } from "@radix-ui/themes";
|
import { Avatar, Badge, Button, Card, Flex, Heading, Table, Text, TextField } from "@radix-ui/themes";
|
||||||
import { SearchIcon, UserRoundXIcon } from "lucide-react";
|
import { SearchIcon, UserRoundXIcon } from "lucide-react";
|
||||||
|
@ -59,7 +59,7 @@ export default function Users() {
|
||||||
<Flex direction='row' gap='4' align='center'>
|
<Flex direction='row' gap='4' align='center'>
|
||||||
<Avatar
|
<Avatar
|
||||||
size="2"
|
size="2"
|
||||||
src={avatarUrl(email)}
|
src={`https://gravatar.com/avatar/${sha256sum(email)}?d=${GRAVATAR_DEFAULT}`}
|
||||||
radius='full'
|
radius='full'
|
||||||
fallback={email.slice(0, 1) || "@"}
|
fallback={email.slice(0, 1) || "@"}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -2,6 +2,11 @@
|
||||||
@tailwind components;
|
@tailwind components;
|
||||||
@tailwind utilities;
|
@tailwind utilities;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--accent-a11);
|
||||||
|
}
|
||||||
|
|
||||||
/* mfw "ui library" doesnt ship styles */
|
/* mfw "ui library" doesnt ship styles */
|
||||||
.ToastViewport {
|
.ToastViewport {
|
||||||
--viewport-padding: 25px;
|
--viewport-padding: 25px;
|
||||||
|
|
|
@ -2,7 +2,7 @@ import type { Metadata } from 'next';
|
||||||
import ThemeWrapper from '@/lib/components/wrapper/ThemeWrapper';
|
import ThemeWrapper from '@/lib/components/wrapper/ThemeWrapper';
|
||||||
import './globals.css';
|
import './globals.css';
|
||||||
import AuthWrapper from '@/lib/components/wrapper/AuthWrapper';
|
import AuthWrapper from '@/lib/components/wrapper/AuthWrapper';
|
||||||
import { getServerSession } from 'next-auth';
|
import { auth } from "@/auth";
|
||||||
import NavigationWrapper from '@/lib/components/wrapper/NavigationWrapper';
|
import NavigationWrapper from '@/lib/components/wrapper/NavigationWrapper';
|
||||||
import BackgroundImage from '@/lib/components/ui/BackgroundImage';
|
import BackgroundImage from '@/lib/components/ui/BackgroundImage';
|
||||||
import ToastProvider from '@/lib/providers/ToastProvider';
|
import ToastProvider from '@/lib/providers/ToastProvider';
|
||||||
|
@ -23,7 +23,7 @@ export default async function RootLayout({
|
||||||
<ThemeWrapper>
|
<ThemeWrapper>
|
||||||
<BackgroundImage />
|
<BackgroundImage />
|
||||||
<ToastProvider>
|
<ToastProvider>
|
||||||
<AuthWrapper session={await getServerSession()}>
|
<AuthWrapper session={await auth()}>
|
||||||
<NavigationWrapper>
|
<NavigationWrapper>
|
||||||
{children}
|
{children}
|
||||||
</NavigationWrapper>
|
</NavigationWrapper>
|
||||||
|
|
|
@ -1,48 +1,2 @@
|
||||||
import NextAuth, { AuthOptions } from "next-auth";
|
import { handlers } from "@/auth";
|
||||||
import CredentialProvider from "next-auth/providers/credentials";
|
export const { GET, POST } = handlers;
|
||||||
import { sha256sum } from "@/lib/util";
|
|
||||||
import { validateCredentials } from "@/lib/db";
|
|
||||||
import { avatarUrl, GRAVATAR_DEFAULT } from "@/lib/constants";
|
|
||||||
import { auditLogRaw } from "@/lib/audit";
|
|
||||||
|
|
||||||
const authOptions: AuthOptions = {
|
|
||||||
providers: [
|
|
||||||
CredentialProvider({
|
|
||||||
name: 'Mail account',
|
|
||||||
credentials: {
|
|
||||||
email: { label: "E-Mail", type: "email", placeholder: "webmistress@amogus.cloud" },
|
|
||||||
password: { label: "Password", type: "password" },
|
|
||||||
},
|
|
||||||
async authorize(credentials, req) {
|
|
||||||
console.log(`[${credentials?.email}] Authentication attempt`);
|
|
||||||
|
|
||||||
if (credentials && await validateCredentials(credentials.email, credentials.password)) {
|
|
||||||
console.log(`[${credentials.email}] Authentication succeeded`);
|
|
||||||
|
|
||||||
await auditLogRaw({
|
|
||||||
user: credentials.email,
|
|
||||||
ts: new Date().toISOString(),
|
|
||||||
action: "login",
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
id: credentials.email,
|
|
||||||
email: credentials.email,
|
|
||||||
image: avatarUrl(credentials.email),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`[${credentials?.email}] Authentication failed`);
|
|
||||||
return null;
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
session: {
|
|
||||||
strategy: 'jwt',
|
|
||||||
maxAge: 4 * 60 * 60 // 4 hours
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const handler = NextAuth(authOptions);
|
|
||||||
|
|
||||||
export { handler as GET, handler as POST };
|
|
||||||
|
|
58
src/auth.ts
Normal file
58
src/auth.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import nextAuth, { CredentialsSignin } from "next-auth";
|
||||||
|
import CredentialProvider from "next-auth/providers/credentials";
|
||||||
|
import WebAuthnProvider from "next-auth/providers/webauthn";
|
||||||
|
import { sha256sum } from "@/lib/util";
|
||||||
|
import { validateCredentials } from "@/lib/db";
|
||||||
|
import { GRAVATAR_DEFAULT } from "@/lib/constants";
|
||||||
|
import { auditLogRaw } from "@/lib/audit";
|
||||||
|
|
||||||
|
export const { auth, handlers, signIn, signOut } = nextAuth({
|
||||||
|
providers: [
|
||||||
|
CredentialProvider({
|
||||||
|
name: "Mail Account",
|
||||||
|
credentials: {
|
||||||
|
email: { label: "E-Mail", type: "email", placeholder: "webmistress@amogus.cloud" },
|
||||||
|
password: { label: "Password", type: "password" },
|
||||||
|
},
|
||||||
|
async authorize(credentials, req) {
|
||||||
|
if (typeof credentials.email != "string" || typeof credentials.password != "string") {
|
||||||
|
console.log("Someone tried to fuck with the auth payload, uh oh");
|
||||||
|
throw new CredentialsSignin("what");
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[${credentials?.email}] Authentication attempt`);
|
||||||
|
|
||||||
|
if (credentials && await validateCredentials(credentials.email, credentials.password)) {
|
||||||
|
console.log(`[${credentials.email}] Authentication succeeded`);
|
||||||
|
|
||||||
|
const emailHash = sha256sum(credentials.email.trim().toLowerCase());
|
||||||
|
|
||||||
|
await auditLogRaw({
|
||||||
|
user: credentials.email,
|
||||||
|
ts: new Date().toISOString(),
|
||||||
|
action: "login",
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: credentials.email,
|
||||||
|
email: credentials.email,
|
||||||
|
image: `https://gravatar.com/avatar/${emailHash}?d=${GRAVATAR_DEFAULT}`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[${credentials?.email}] Authentication failed`);
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
WebAuthnProvider({
|
||||||
|
name: "Passkey",
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
session: {
|
||||||
|
strategy: 'jwt',
|
||||||
|
maxAge: 4 * 60 * 60, // 4 hours
|
||||||
|
},
|
||||||
|
experimental: {
|
||||||
|
enableWebAuthn: true,
|
||||||
|
}
|
||||||
|
});
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
import crypto from "crypto";
|
import crypto from "crypto";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
import { getServerSession } from "next-auth";
|
import { auth } from "@/auth";
|
||||||
import { AuditLog, auditLog } from "./audit";
|
import { AuditLog, auditLog } from "./audit";
|
||||||
import { AliasEntry, AliasRequestEntry, ApiKeyEntry, approveAliasEntry, createAliasEntry, createApiKeyEntry, createTempAliasRequestEntry, createUserEntry, database, deleteAliasEntry, deleteApiKey, deleteTempAliasRequestEntry, getAlias, getAllAliases, getApiKeyById, getTempAliasRequestEntry, getUserAliases, getUserApiKeys, isAliasAvailable, setUserPassword } from "./db";
|
import { AliasEntry, AliasRequestEntry, ApiKeyEntry, approveAliasEntry, createAliasEntry, createApiKeyEntry, createTempAliasRequestEntry, createUserEntry, database, deleteAliasEntry, deleteApiKey, deleteTempAliasRequestEntry, getAlias, getAllAliases, getApiKeyById, getTempAliasRequestEntry, getUserAliases, getUserApiKeys, isAliasAvailable, setUserPassword } from "./db";
|
||||||
import { aliasesNeedApproval, anonymizeApiKey, isAdmin } from "./util";
|
import { aliasesNeedApproval, anonymizeApiKey, isAdmin } from "./util";
|
||||||
|
@ -10,7 +10,7 @@ import { generateAliasEmail } from "./util-server";
|
||||||
|
|
||||||
export async function fetchAllUsers(): Promise<string[]> {
|
export async function fetchAllUsers(): Promise<string[]> {
|
||||||
return new Promise(async (resolve, reject) => {
|
return new Promise(async (resolve, reject) => {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!isAdmin(session)) return reject("Unauthenticated");
|
if (!isAdmin(session)) return reject("Unauthenticated");
|
||||||
|
|
||||||
const db = database('credentials');
|
const db = database('credentials');
|
||||||
|
@ -24,20 +24,20 @@ export async function fetchAllUsers(): Promise<string[]> {
|
||||||
|
|
||||||
export async function changeOwnPassword(newPass: string) {
|
export async function changeOwnPassword(newPass: string) {
|
||||||
if (newPass.length < 8) throw new Error("Invalid password");
|
if (newPass.length < 8) throw new Error("Invalid password");
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
await setUserPassword(session.user.email, newPass);
|
await setUserPassword(session.user.email, newPass);
|
||||||
auditLog("changeOwnPassword");
|
auditLog("changeOwnPassword");
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchOwnAliases(tempAliases?: boolean) {
|
export async function fetchOwnAliases(tempAliases?: boolean) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
return await getUserAliases(session.user.email, tempAliases);
|
return await getUserAliases(session.user.email, tempAliases);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchUserAliases(email: string) {
|
export async function fetchUserAliases(email: string) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
if (!isAdmin(session)) throw new Error("Unauthorized");
|
if (!isAdmin(session)) throw new Error("Unauthorized");
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ export async function fetchUserAliases(email: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchAllAliases() {
|
export async function fetchAllAliases() {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
if (!isAdmin(session)) throw new Error("Unauthorized");
|
if (!isAdmin(session)) throw new Error("Unauthorized");
|
||||||
|
|
||||||
|
@ -53,14 +53,14 @@ export async function fetchAllAliases() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function aliasAvailable(email: string, searchTempRequests: boolean = false) {
|
export async function aliasAvailable(email: string, searchTempRequests: boolean = false) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user) throw new Error("Unauthenticated");
|
if (!session?.user) throw new Error("Unauthenticated");
|
||||||
|
|
||||||
return await isAliasAvailable(email, searchTempRequests);
|
return await isAliasAvailable(email, searchTempRequests);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAlias(user: string, alias: string): Promise<AliasEntry> {
|
export async function createAlias(user: string, alias: string): Promise<AliasEntry> {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
if (!isAdmin(session)) throw new Error("Unauthenticated");
|
if (!isAdmin(session)) throw new Error("Unauthenticated");
|
||||||
if (!await isAliasAvailable(alias)) throw new Error("Alias unavailable");
|
if (!await isAliasAvailable(alias)) throw new Error("Alias unavailable");
|
||||||
|
@ -80,7 +80,7 @@ export async function createAlias(user: string, alias: string): Promise<AliasEnt
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createAliasSelf(alias: string): Promise<AliasEntry> {
|
export async function createAliasSelf(alias: string): Promise<AliasEntry> {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
|
|
||||||
const pending = aliasesNeedApproval(session);
|
const pending = aliasesNeedApproval(session);
|
||||||
|
@ -105,7 +105,7 @@ export async function requestTemporaryAlias(
|
||||||
style: 'words' | 'random',
|
style: 'words' | 'random',
|
||||||
oldToken?: string,
|
oldToken?: string,
|
||||||
): Promise<AliasRequestEntry> {
|
): Promise<AliasRequestEntry> {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
|
|
||||||
if (!label.length || label.length > 16) throw new Error("Malformed request");
|
if (!label.length || label.length > 16) throw new Error("Malformed request");
|
||||||
|
@ -134,7 +134,7 @@ export async function requestTemporaryAlias(
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function claimTemporaryAlias(key: string): Promise<AliasEntry> {
|
export async function claimTemporaryAlias(key: string): Promise<AliasEntry> {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
|
|
||||||
const data = await getTempAliasRequestEntry(key);
|
const data = await getTempAliasRequestEntry(key);
|
||||||
|
@ -159,7 +159,7 @@ export async function claimTemporaryAlias(key: string): Promise<AliasEntry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function disposeTempAliasRequest(key: string) {
|
export async function disposeTempAliasRequest(key: string) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
|
|
||||||
const data = await getTempAliasRequestEntry(key);
|
const data = await getTempAliasRequestEntry(key);
|
||||||
|
@ -170,7 +170,7 @@ export async function disposeTempAliasRequest(key: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteAlias(alias: string) {
|
export async function deleteAlias(alias: string) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
|
|
||||||
if (!isAdmin(session) && (await getAlias(alias))?.address != session.user.email) {
|
if (!isAdmin(session) && (await getAlias(alias))?.address != session.user.email) {
|
||||||
|
@ -182,7 +182,7 @@ export async function deleteAlias(alias: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function approveAlias(alias: string) {
|
export async function approveAlias(alias: string) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
if (!isAdmin(session)) throw new Error("Unauthorized");
|
if (!isAdmin(session)) throw new Error("Unauthorized");
|
||||||
|
|
||||||
|
@ -191,7 +191,7 @@ export async function approveAlias(alias: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createUser(email: string, password: string) {
|
export async function createUser(email: string, password: string) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
if (!isAdmin(session)) throw new Error("Unauthorized");
|
if (!isAdmin(session)) throw new Error("Unauthorized");
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ export async function createUser(email: string, password: string) {
|
||||||
export async function fetchAuditLog(page: number): Promise<{ page: number, perPage: number, totalItems: number, items: (AuditLog & { index: number })[] }> {
|
export async function fetchAuditLog(page: number): Promise<{ page: number, perPage: number, totalItems: number, items: (AuditLog & { index: number })[] }> {
|
||||||
const itemsPerPage = 10;
|
const itemsPerPage = 10;
|
||||||
|
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
if (!isAdmin(session)) throw new Error("Unauthorized");
|
if (!isAdmin(session)) throw new Error("Unauthorized");
|
||||||
|
|
||||||
|
@ -239,7 +239,7 @@ export async function fetchAuditLog(page: number): Promise<{ page: number, perPa
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function createApiKey(label: string): Promise<ApiKeyEntry> {
|
export async function createApiKey(label: string): Promise<ApiKeyEntry> {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
|
|
||||||
if (!label.length || label.length > 64) throw new Error("Malformed label");
|
if (!label.length || label.length > 64) throw new Error("Malformed label");
|
||||||
|
@ -260,7 +260,7 @@ export async function createApiKey(label: string): Promise<ApiKeyEntry> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchOwnApiKeys(): Promise<ApiKeyEntry[]> {
|
export async function fetchOwnApiKeys(): Promise<ApiKeyEntry[]> {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
|
|
||||||
const keys = await getUserApiKeys(session.user.email);
|
const keys = await getUserApiKeys(session.user.email);
|
||||||
|
@ -268,7 +268,7 @@ export async function fetchOwnApiKeys(): Promise<ApiKeyEntry[]> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deleteOwnApikey(id: number) {
|
export async function deleteOwnApikey(id: number) {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
if (!session?.user?.email) throw new Error("Unauthenticated");
|
if (!session?.user?.email) throw new Error("Unauthenticated");
|
||||||
|
|
||||||
const key = await getApiKeyById(id);
|
const key = await getApiKeyById(id);
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { getServerSession } from "next-auth";
|
import { auth } from "@/auth";
|
||||||
import fs from "fs/promises";
|
import fs from "fs/promises";
|
||||||
|
|
||||||
export type AuditLogAction = 'login' | 'changeOwnPassword' | 'requestAlias' | 'deleteAlias' | 'createTempAlias' // Unprivileged
|
export type AuditLogAction = 'login' | 'changeOwnPassword' | 'requestAlias' | 'deleteAlias' | 'createTempAlias' // Unprivileged
|
||||||
|
@ -15,7 +15,7 @@ export type AuditLog = {
|
||||||
export function auditLog(action: AuditLogAction, data?: any) {
|
export function auditLog(action: AuditLogAction, data?: any) {
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
const session = await getServerSession();
|
const session = await auth();
|
||||||
|
|
||||||
const log: AuditLog = {
|
const log: AuditLog = {
|
||||||
user: session?.user?.email,
|
user: session?.user?.email,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { avatarUrl, GRAVATAR_DEFAULT } from "@/lib/constants";
|
import { GRAVATAR_DEFAULT } from "@/lib/constants";
|
||||||
import { AliasEntry } from "@/lib/db";
|
import { AliasEntry } from "@/lib/db";
|
||||||
import { isAdmin, sha256sum } from "@/lib/util";
|
import { isAdmin, sha256sum } from "@/lib/util";
|
||||||
import { Avatar, Badge, Button, Card, Code, Dialog, Flex, Grid, Heading, Table, Text } from "@radix-ui/themes";
|
import { Avatar, Badge, Button, Card, Code, Dialog, Flex, Grid, Heading, Table, Text } from "@radix-ui/themes";
|
||||||
|
@ -36,7 +36,7 @@ export default function ManageUserButton({ email }: { email: string }) {
|
||||||
<Flex direction="row" gap="3" align="center">
|
<Flex direction="row" gap="3" align="center">
|
||||||
<Avatar
|
<Avatar
|
||||||
size="4"
|
size="4"
|
||||||
src={avatarUrl(email)}
|
src={`https://gravatar.com/avatar/${sha256sum(email)}?d=${GRAVATAR_DEFAULT}`}
|
||||||
fallback={email.slice(0, 1) || "@"}
|
fallback={email.slice(0, 1) || "@"}
|
||||||
/>
|
/>
|
||||||
<Flex direction="column">
|
<Flex direction="column">
|
||||||
|
|
|
@ -2,9 +2,10 @@
|
||||||
|
|
||||||
import { changeOwnPassword } from "@/lib/actions";
|
import { changeOwnPassword } from "@/lib/actions";
|
||||||
import { ToastContext } from "@/lib/providers/ToastProvider";
|
import { ToastContext } from "@/lib/providers/ToastProvider";
|
||||||
import { Box, Button, Callout, Card, Dialog, Flex, Heading, IconButton, Link, Popover, Text, TextField, Tooltip } from "@radix-ui/themes";
|
import { Box, Button, Callout, Card, Dialog, Flex, Heading, IconButton, Popover, Tabs, Text, TextField, Tooltip } from "@radix-ui/themes";
|
||||||
import { AlertCircleIcon, InfoIcon, LockIcon, UserIcon } from "lucide-react";
|
import { AlertCircleIcon, InfoIcon, LockIcon, UserIcon } from "lucide-react";
|
||||||
import { useSession } from "next-auth/react";
|
import { useSession } from "next-auth/react";
|
||||||
|
import Link from "next/link";
|
||||||
import { useContext, useState } from "react";
|
import { useContext, useState } from "react";
|
||||||
|
|
||||||
export default function OwnCredentialsCard() {
|
export default function OwnCredentialsCard() {
|
||||||
|
@ -16,8 +17,16 @@ export default function OwnCredentialsCard() {
|
||||||
return (
|
return (
|
||||||
<Card className="h-fit">
|
<Card className="h-fit">
|
||||||
<Heading size="3">Credentials</Heading>
|
<Heading size="3">Credentials</Heading>
|
||||||
<Text weight="light" size="2">These are the details you can use to authenticate via SMTP and IMAP.</Text>
|
<Text weight="light" size="2">These are the details you can use to authenticate to the webmail, settings panel and via other mail clients.</Text>
|
||||||
|
|
||||||
|
<Tabs.Root defaultValue="password">
|
||||||
|
<Tabs.List size="2">
|
||||||
|
<Tabs.Trigger value="password">Password</Tabs.Trigger>
|
||||||
|
<Tabs.Trigger value="passkey">Passkeys</Tabs.Trigger>
|
||||||
|
</Tabs.List>
|
||||||
|
|
||||||
|
<Box pt="3" pb="2">
|
||||||
|
<Tabs.Content value="password">
|
||||||
<Flex direction="column" gap="3" className="pt-2">
|
<Flex direction="column" gap="3" className="pt-2">
|
||||||
<TextField.Root>
|
<TextField.Root>
|
||||||
<TextField.Slot>
|
<TextField.Slot>
|
||||||
|
@ -120,6 +129,21 @@ export default function OwnCredentialsCard() {
|
||||||
</Dialog.Root>
|
</Dialog.Root>
|
||||||
</Flex>
|
</Flex>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</Tabs.Content>
|
||||||
|
<Tabs.Content value="passkey">
|
||||||
|
<Card>
|
||||||
|
<Text weight="light" size="2">
|
||||||
|
You can use a passkey (<Link href="https://bitwarden.com/passwordless-passkeys/">software</Link> or
|
||||||
|
<Link href="https://www.yubico.com/products/yubikey-5-overview/">hardware</Link> based) instead of
|
||||||
|
your password to sign into the web UI. However, they are not compatible with the webmail or external
|
||||||
|
mail clients.
|
||||||
|
</Text>
|
||||||
|
</Card>
|
||||||
|
</Tabs.Content>
|
||||||
|
</Box>
|
||||||
|
</Tabs.Root>
|
||||||
|
|
||||||
|
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
}
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { disposeTempAliasRequest, fetchOwnAliases, requestTemporaryAlias, claimTemporaryAlias, deleteAlias } from "@/lib/actions";
|
import { disposeTempAliasRequest, fetchOwnAliases, requestTemporaryAlias, claimTemporaryAlias, deleteAlias } from "@/lib/actions";
|
||||||
import { avatarUrl, GRAVATAR_DEFAULT } from "@/lib/constants";
|
import { GRAVATAR_DEFAULT } from "@/lib/constants";
|
||||||
import { AliasEntry, AliasRequestEntry } from "@/lib/db";
|
import { AliasEntry, AliasRequestEntry } from "@/lib/db";
|
||||||
import { ToastContext } from "@/lib/providers/ToastProvider";
|
import { ToastContext } from "@/lib/providers/ToastProvider";
|
||||||
import { sha256sum } from "@/lib/util";
|
import { sha256sum } from "@/lib/util";
|
||||||
|
@ -120,7 +120,7 @@ export default function TempAliasesCard() {
|
||||||
<Flex direction="row" align="center" gap="3">
|
<Flex direction="row" align="center" gap="3">
|
||||||
<Avatar
|
<Avatar
|
||||||
size="3"
|
size="3"
|
||||||
src={avatarUrl(aliasPreview?.alias || "")}
|
src={`https://gravatar.com/avatar/${sha256sum(aliasPreview?.alias || "")}?d=${GRAVATAR_DEFAULT}`}
|
||||||
fallback={"@"}
|
fallback={"@"}
|
||||||
/>
|
/>
|
||||||
<Flex direction="column" gap="0">
|
<Flex direction="column" gap="0">
|
||||||
|
@ -171,7 +171,7 @@ export default function TempAliasesCard() {
|
||||||
<Flex direction="row" align="center" gap="3">
|
<Flex direction="row" align="center" gap="3">
|
||||||
<Avatar
|
<Avatar
|
||||||
size="3"
|
size="3"
|
||||||
src={avatarUrl(aliasPreview!.alias)}
|
src={`https://gravatar.com/avatar/${sha256sum(aliasPreview!.alias)}?d=${GRAVATAR_DEFAULT}`}
|
||||||
fallback={"@"}
|
fallback={"@"}
|
||||||
/>
|
/>
|
||||||
<Flex direction="column" gap="0">
|
<Flex direction="column" gap="0">
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import { sha256sum } from "./util";
|
|
||||||
|
|
||||||
|
|
||||||
// TODO read these from environment
|
// TODO read these from environment
|
||||||
|
@ -11,8 +10,3 @@ export const WEBMAIL_URL = "https://webmail.amogus.cloud";
|
||||||
export const ALIAS_DOMAINS = ["amogus.cloud", "lea.pet", "futacockinside.me"];
|
export const ALIAS_DOMAINS = ["amogus.cloud", "lea.pet", "futacockinside.me"];
|
||||||
export const GRAVATAR_DEFAULT = "retro";
|
export const GRAVATAR_DEFAULT = "retro";
|
||||||
export const TEMP_EMAIL_DOMAIN = "t.amogus.cloud";
|
export const TEMP_EMAIL_DOMAIN = "t.amogus.cloud";
|
||||||
|
|
||||||
export function avatarUrl(email: string) {
|
|
||||||
//return `https://gravatar.com/avatar/${sha256sum(email)}?d=${GRAVATAR_DEFAULT}`;
|
|
||||||
return `https://picvatar.lea.pet/generate/47882/${sha256sum(email)}?gravatar=1`;
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue