Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e89e313712 | ||
|
|
c2ff658182 | ||
|
|
02cd98fa9c | ||
|
|
d327bc355c | ||
|
|
8ae631a626 | ||
|
|
1d8dc8fe5b | ||
|
|
688ae6c86e | ||
|
|
21809843cd | ||
|
|
b088a5ef2a | ||
|
|
c502cd58db | ||
|
|
97e7d7190d | ||
|
|
38919003e9 | ||
|
|
f15a8dc277 | ||
|
|
92927b1373 | ||
|
|
6a4108ed61 |
27
CHANGELOG.md
27
CHANGELOG.md
@@ -1,3 +1,30 @@
|
||||
## [0.19.0](https://github.com/stonith404/pingvin-share/compare/v0.18.2...v0.19.0) (2023-10-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **auth:** add OAuth2 login ([#276](https://github.com/stonith404/pingvin-share/issues/276)) ([02cd98f](https://github.com/stonith404/pingvin-share/commit/02cd98fa9cf9865d91494848aabaf42b19e4957b)), closes [#278](https://github.com/stonith404/pingvin-share/issues/278) [#279](https://github.com/stonith404/pingvin-share/issues/279) [#281](https://github.com/stonith404/pingvin-share/issues/281)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* delete unfinished shares after a day ([d327bc3](https://github.com/stonith404/pingvin-share/commit/d327bc355c8583231e058731934cf51ab25d9ce5))
|
||||
|
||||
## [0.18.2](https://github.com/stonith404/pingvin-share/compare/v0.18.1...v0.18.2) (2023-10-09)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* disable image optimizations for logo to prevent caching issues with custom logos ([3891900](https://github.com/stonith404/pingvin-share/commit/38919003e9091203b507d0f0b061f4a1835ff4f4))
|
||||
* memory leak while downloading large files ([97e7d71](https://github.com/stonith404/pingvin-share/commit/97e7d7190dfe219caf441dffcd7830c304c3c939))
|
||||
|
||||
## [0.18.1](https://github.com/stonith404/pingvin-share/compare/v0.18.0...v0.18.1) (2023-09-22)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* permission changes of docker container brakes existing installations ([6a4108e](https://github.com/stonith404/pingvin-share/commit/6a4108ed6138e7297e66fd1e38450f23afe99aae))
|
||||
|
||||
## [0.18.0](https://github.com/stonith404/pingvin-share/compare/v0.17.5...v0.18.0) (2023-09-21)
|
||||
|
||||
|
||||
|
||||
12
Dockerfile
12
Dockerfile
@@ -13,6 +13,7 @@ RUN npm run build
|
||||
|
||||
# Stage 3: Backend dependencies
|
||||
FROM node:20-alpine AS backend-dependencies
|
||||
RUN apk add --no-cache python3
|
||||
WORKDIR /opt/app
|
||||
COPY backend/package.json backend/package-lock.json ./
|
||||
RUN npm ci
|
||||
@@ -32,14 +33,9 @@ ENV NODE_ENV=docker
|
||||
# Alpine specific dependencies
|
||||
RUN apk update --no-cache
|
||||
RUN apk upgrade --no-cache
|
||||
RUN apk add --no-cache curl
|
||||
RUN apk add --no-cache curl nginx
|
||||
|
||||
# Set user and group IDs for the node user
|
||||
ARG UID=1000
|
||||
ARG GID=1000
|
||||
RUN deluser node
|
||||
RUN adduser -u $UID -g $GID node -D
|
||||
USER node
|
||||
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
WORKDIR /opt/app/frontend
|
||||
COPY --from=frontend-builder /opt/app/public ./public
|
||||
@@ -62,4 +58,4 @@ HEALTHCHECK --interval=10s --timeout=3s CMD curl -f http://localhost:3000/api/he
|
||||
|
||||
# Application startup
|
||||
# HOSTNAME=0.0.0.0 fixes https://github.com/vercel/next.js/issues/51684. It can be removed as soon as the issue is fixed
|
||||
CMD cp -rn /tmp/img /opt/app/frontend/public && HOSTNAME=0.0.0.0 node frontend/server.js & cd backend && npm run prod
|
||||
CMD cp -rn /tmp/img /opt/app/frontend/public && nginx && PORT=3333 HOSTNAME=0.0.0.0 node frontend/server.js & cd backend && npm run prod
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
|
||||
_Read this in another language: [Spanish](/docs/README.es.md), [English](/README.md), [Simplified Chinese](/docs/README.zh-cn.md)_
|
||||
_Read this in another language: [Spanish](/docs/README.es.md), [English](/README.md), [Simplified Chinese](/docs/README.zh-cn.md), [日本語](/docs/README.ja-jp.md)_
|
||||
|
||||
---
|
||||
|
||||
@@ -63,6 +63,8 @@ npm run build
|
||||
pm2 start --name="pingvin-share-frontend" npm -- run start
|
||||
```
|
||||
|
||||
**Uploading Large Files**: By default, Pingvin Share uses a built-in reverse proxy to reduce the installation steps. However, this reverse proxy is not optimized for uploading large files. If you wish to upload larger files, you can either use the Docker installation or set up your own reverse proxy. An example configuration for Nginx can be found in `/nginx/nginx.conf`.
|
||||
|
||||
The website is now listening on `http://localhost:3000`, have fun with Pingvin Share 🐧!
|
||||
|
||||
### Integrations
|
||||
@@ -77,6 +79,10 @@ ClamAV is used to scan shares for malicious files and remove them if found.
|
||||
|
||||
Please note that ClamAV needs a lot of [ressources](https://docs.clamav.net/manual/Installing/Docker.html#memory-ram-requirements).
|
||||
|
||||
#### OAuth 2 Login
|
||||
|
||||
View the [OAuth 2 guide](/docs/oauth2-guide.md) for more information.
|
||||
|
||||
### Additional resources
|
||||
|
||||
- [Synology NAS installation](https://mariushosting.com/how-to-install-pingvin-share-on-your-synology-nas/)
|
||||
|
||||
130
backend/package-lock.json
generated
130
backend/package-lock.json
generated
@@ -1,13 +1,14 @@
|
||||
{
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"dependencies": {
|
||||
"@nestjs/cache-manager": "^2.1.0",
|
||||
"@nestjs/common": "^10.1.2",
|
||||
"@nestjs/config": "^3.0.0",
|
||||
"@nestjs/core": "^10.1.2",
|
||||
@@ -21,6 +22,7 @@
|
||||
"archiver": "^5.3.1",
|
||||
"argon2": "^0.30.3",
|
||||
"body-parser": "^1.20.2",
|
||||
"cache-manager": "^5.2.4",
|
||||
"clamscan": "^2.1.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
@@ -28,6 +30,8 @@
|
||||
"cookie-parser": "^1.4.6",
|
||||
"mime-types": "^2.1.35",
|
||||
"moment": "^2.29.4",
|
||||
"nanoid": "^3.3.6",
|
||||
"node-fetch": "^2.7.0",
|
||||
"nodemailer": "^6.9.4",
|
||||
"otplib": "^12.0.1",
|
||||
"passport": "^0.6.0",
|
||||
@@ -52,6 +56,7 @@
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^20.4.5",
|
||||
"@types/node-fetch": "^2.6.6",
|
||||
"@types/nodemailer": "^6.4.9",
|
||||
"@types/passport-jwt": "^3.0.9",
|
||||
"@types/qrcode-svg": "^1.1.1",
|
||||
@@ -622,6 +627,18 @@
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/cache-manager": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@nestjs/cache-manager/-/cache-manager-2.1.0.tgz",
|
||||
"integrity": "sha512-9kep3a8Mq5cMuXN/anGhSYc0P48CRBXk5wyJJRBFxhNkCH8AIzZF4CASGVDIEMmm3OjVcEUHojjyJwCODS17Qw==",
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": "^9.0.0 || ^10.0.0",
|
||||
"@nestjs/core": "^9.0.0 || ^10.0.0",
|
||||
"cache-manager": "<=5",
|
||||
"reflect-metadata": "^0.1.12",
|
||||
"rxjs": "^7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@nestjs/cli": {
|
||||
"version": "10.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.1.10.tgz",
|
||||
@@ -1438,6 +1455,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz",
|
||||
"integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg=="
|
||||
},
|
||||
"node_modules/@types/node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/nodemailer": {
|
||||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz",
|
||||
@@ -2525,6 +2552,23 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/cache-manager": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/cache-manager/-/cache-manager-5.2.4.tgz",
|
||||
"integrity": "sha512-gkuCjug16NdGvKm/sydxGVx17uffrSWcEe2xraBtwRCgdYcFxwJAla4OYpASAZT2yhSoxgDiWL9XH6IAChcZJA==",
|
||||
"dependencies": {
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lru-cache": "^10.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/cache-manager/node_modules/lru-cache": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.0.1.tgz",
|
||||
"integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==",
|
||||
"engines": {
|
||||
"node": "14 || >=16.14"
|
||||
}
|
||||
},
|
||||
"node_modules/call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
@@ -5248,6 +5292,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"node_modules/lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||
},
|
||||
"node_modules/lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
@@ -5572,6 +5621,17 @@
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"bin": {
|
||||
"nanoid": "bin/nanoid.cjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
@@ -5733,9 +5793,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
@@ -7833,7 +7893,7 @@
|
||||
},
|
||||
"node_modules/tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"node_modules/tree-kill": {
|
||||
@@ -8235,7 +8295,7 @@
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"node_modules/webpack": {
|
||||
@@ -8305,7 +8365,7 @@
|
||||
},
|
||||
"node_modules/whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"dependencies": {
|
||||
"tr46": "~0.0.3",
|
||||
@@ -8951,6 +9011,12 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"@nestjs/cache-manager": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmmirror.com/@nestjs/cache-manager/-/cache-manager-2.1.0.tgz",
|
||||
"integrity": "sha512-9kep3a8Mq5cMuXN/anGhSYc0P48CRBXk5wyJJRBFxhNkCH8AIzZF4CASGVDIEMmm3OjVcEUHojjyJwCODS17Qw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@nestjs/cli": {
|
||||
"version": "10.1.10",
|
||||
"resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.1.10.tgz",
|
||||
@@ -9542,6 +9608,16 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.5.tgz",
|
||||
"integrity": "sha512-rt40Nk13II9JwQBdeYqmbn2Q6IVTA5uPhvSO+JVqdXw/6/4glI6oR9ezty/A9Hg5u7JH4OmYmuQ+XvjKm0Datg=="
|
||||
},
|
||||
"@types/node-fetch": {
|
||||
"version": "2.6.6",
|
||||
"resolved": "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.6.tgz",
|
||||
"integrity": "sha512-95X8guJYhfqiuVVhRFxVQcf4hW/2bCuoPwDasMf/531STFoNoWTT7YDnWdXHEZKqAGUigmpG31r2FE70LwnzJw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*",
|
||||
"form-data": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"@types/nodemailer": {
|
||||
"version": "6.4.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.9.tgz",
|
||||
@@ -10386,6 +10462,22 @@
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg=="
|
||||
},
|
||||
"cache-manager": {
|
||||
"version": "5.2.4",
|
||||
"resolved": "https://registry.npmmirror.com/cache-manager/-/cache-manager-5.2.4.tgz",
|
||||
"integrity": "sha512-gkuCjug16NdGvKm/sydxGVx17uffrSWcEe2xraBtwRCgdYcFxwJAla4OYpASAZT2yhSoxgDiWL9XH6IAChcZJA==",
|
||||
"requires": {
|
||||
"lodash.clonedeep": "^4.5.0",
|
||||
"lru-cache": "^10.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": {
|
||||
"version": "10.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-10.0.1.tgz",
|
||||
"integrity": "sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"call-bind": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
|
||||
@@ -12394,6 +12486,11 @@
|
||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||
},
|
||||
"lodash.clonedeep": {
|
||||
"version": "4.5.0",
|
||||
"resolved": "https://registry.npmmirror.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
|
||||
"integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ=="
|
||||
},
|
||||
"lodash.defaults": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
|
||||
@@ -12636,6 +12733,11 @@
|
||||
"integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==",
|
||||
"dev": true
|
||||
},
|
||||
"nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA=="
|
||||
},
|
||||
"napi-build-utils": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
|
||||
@@ -12767,9 +12869,9 @@
|
||||
}
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"version": "2.7.0",
|
||||
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.7.0.tgz",
|
||||
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
|
||||
"requires": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
}
|
||||
@@ -14306,7 +14408,7 @@
|
||||
},
|
||||
"tr46": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
|
||||
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
|
||||
},
|
||||
"tree-kill": {
|
||||
@@ -14586,7 +14688,7 @@
|
||||
},
|
||||
"webidl-conversions": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
|
||||
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
|
||||
},
|
||||
"webpack": {
|
||||
@@ -14635,7 +14737,7 @@
|
||||
},
|
||||
"whatwg-url": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
|
||||
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
|
||||
"requires": {
|
||||
"tr46": "~0.0.3",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-backend",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"dev": "cross-env NODE_ENV=development nest start --watch",
|
||||
@@ -13,6 +13,7 @@
|
||||
"seed": "ts-node prisma/seed/config.seed.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/cache-manager": "^2.1.0",
|
||||
"@nestjs/common": "^10.1.2",
|
||||
"@nestjs/config": "^3.0.0",
|
||||
"@nestjs/core": "^10.1.2",
|
||||
@@ -26,6 +27,7 @@
|
||||
"archiver": "^5.3.1",
|
||||
"argon2": "^0.30.3",
|
||||
"body-parser": "^1.20.2",
|
||||
"cache-manager": "^5.2.4",
|
||||
"clamscan": "^2.1.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
@@ -33,6 +35,8 @@
|
||||
"cookie-parser": "^1.4.6",
|
||||
"mime-types": "^2.1.35",
|
||||
"moment": "^2.29.4",
|
||||
"nanoid": "^3.3.6",
|
||||
"node-fetch": "^2.7.0",
|
||||
"nodemailer": "^6.9.4",
|
||||
"otplib": "^12.0.1",
|
||||
"passport": "^0.6.0",
|
||||
@@ -57,6 +61,7 @@
|
||||
"@types/mime-types": "^2.1.1",
|
||||
"@types/multer": "^1.4.7",
|
||||
"@types/node": "^20.4.5",
|
||||
"@types/node-fetch": "^2.6.6",
|
||||
"@types/nodemailer": "^6.4.9",
|
||||
"@types/passport-jwt": "^3.0.9",
|
||||
"@types/qrcode-svg": "^1.1.1",
|
||||
|
||||
31
backend/prisma/migrations/20231021165436_oauth/migration.sql
Normal file
31
backend/prisma/migrations/20231021165436_oauth/migration.sql
Normal file
@@ -0,0 +1,31 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "OAuthUser" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"provider" TEXT NOT NULL,
|
||||
"providerUserId" TEXT NOT NULL,
|
||||
"providerUsername" TEXT NOT NULL,
|
||||
"userId" TEXT NOT NULL,
|
||||
CONSTRAINT "OAuthUser_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
-- RedefineTables
|
||||
PRAGMA foreign_keys=OFF;
|
||||
CREATE TABLE "new_User" (
|
||||
"id" TEXT NOT NULL PRIMARY KEY,
|
||||
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" DATETIME NOT NULL,
|
||||
"username" TEXT NOT NULL,
|
||||
"email" TEXT NOT NULL,
|
||||
"password" TEXT,
|
||||
"isAdmin" BOOLEAN NOT NULL DEFAULT false,
|
||||
"totpEnabled" BOOLEAN NOT NULL DEFAULT false,
|
||||
"totpVerified" BOOLEAN NOT NULL DEFAULT false,
|
||||
"totpSecret" TEXT
|
||||
);
|
||||
INSERT INTO "new_User" ("createdAt", "email", "id", "isAdmin", "password", "totpEnabled", "totpSecret", "totpVerified", "updatedAt", "username") SELECT "createdAt", "email", "id", "isAdmin", "password", "totpEnabled", "totpSecret", "totpVerified", "updatedAt", "username" FROM "User";
|
||||
DROP TABLE "User";
|
||||
ALTER TABLE "new_User" RENAME TO "User";
|
||||
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
|
||||
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
||||
PRAGMA foreign_key_check;
|
||||
PRAGMA foreign_keys=ON;
|
||||
@@ -14,7 +14,7 @@ model User {
|
||||
|
||||
username String @unique
|
||||
email String @unique
|
||||
password String
|
||||
password String?
|
||||
isAdmin Boolean @default(false)
|
||||
|
||||
shares Share[]
|
||||
@@ -26,6 +26,8 @@ model User {
|
||||
totpVerified Boolean @default(false)
|
||||
totpSecret String?
|
||||
resetPasswordToken ResetPasswordToken?
|
||||
|
||||
oAuthUsers OAuthUser[]
|
||||
}
|
||||
|
||||
model RefreshToken {
|
||||
@@ -60,6 +62,15 @@ model ResetPasswordToken {
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model OAuthUser {
|
||||
id String @id @default(uuid())
|
||||
provider String
|
||||
providerUserId String
|
||||
providerUsername String
|
||||
userId String
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
}
|
||||
|
||||
model Share {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now())
|
||||
@@ -134,7 +145,7 @@ model Config {
|
||||
name String
|
||||
category String
|
||||
type String
|
||||
defaultValue String @default("")
|
||||
defaultValue String @default("")
|
||||
value String?
|
||||
obscured Boolean @default(false)
|
||||
secret Boolean @default(true)
|
||||
|
||||
@@ -119,6 +119,89 @@ const configVariables: ConfigVariables = {
|
||||
obscured: true,
|
||||
},
|
||||
},
|
||||
oauth: {
|
||||
"allowRegistration": {
|
||||
type: "boolean",
|
||||
defaultValue: "true",
|
||||
},
|
||||
"ignoreTotp": {
|
||||
type: "boolean",
|
||||
defaultValue: "true",
|
||||
},
|
||||
"github-enabled": {
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
},
|
||||
"github-clientId": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
"github-clientSecret": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
obscured: true,
|
||||
},
|
||||
"google-enabled": {
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
},
|
||||
"google-clientId": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
"google-clientSecret": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
obscured: true,
|
||||
},
|
||||
"microsoft-enabled": {
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
},
|
||||
"microsoft-tenant": {
|
||||
type: "string",
|
||||
defaultValue: "common",
|
||||
},
|
||||
"microsoft-clientId": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
"microsoft-clientSecret": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
obscured: true,
|
||||
},
|
||||
"discord-enabled": {
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
},
|
||||
"discord-clientId": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
"discord-clientSecret": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
obscured: true,
|
||||
},
|
||||
"oidc-enabled": {
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
},
|
||||
"oidc-discoveryUri": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
"oidc-clientId": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
"oidc-clientSecret": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
obscured: true,
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
type ConfigVariables = {
|
||||
@@ -175,7 +258,7 @@ async function migrateConfigVariables() {
|
||||
const configVariable =
|
||||
configVariables[existingConfigVariable.category]?.[
|
||||
existingConfigVariable.name
|
||||
];
|
||||
];
|
||||
if (!configVariable) {
|
||||
await prisma.config.delete({
|
||||
where: {
|
||||
|
||||
19
backend/src/app.controller.ts
Normal file
19
backend/src/app.controller.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { Controller, Get, Res } from "@nestjs/common";
|
||||
import { Response } from "express";
|
||||
import { PrismaService } from "./prisma/prisma.service";
|
||||
|
||||
@Controller("/")
|
||||
export class AppController {
|
||||
constructor(private prismaService: PrismaService) {}
|
||||
|
||||
@Get("health")
|
||||
async health(@Res({ passthrough: true }) res: Response) {
|
||||
try {
|
||||
await this.prismaService.config.findMany();
|
||||
return "OK";
|
||||
} catch {
|
||||
res.statusCode = 500;
|
||||
return "ERROR";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,9 @@ import { ShareModule } from "./share/share.module";
|
||||
import { UserModule } from "./user/user.module";
|
||||
import { ClamScanModule } from "./clamscan/clamscan.module";
|
||||
import { ReverseShareModule } from "./reverseShare/reverseShare.module";
|
||||
import { AppController } from "./app.controller";
|
||||
import { OAuthModule } from "./oauth/oauth.module";
|
||||
import { CacheModule } from "@nestjs/cache-manager";
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -32,7 +35,12 @@ import { ReverseShareModule } from "./reverseShare/reverseShare.module";
|
||||
ScheduleModule.forRoot(),
|
||||
ClamScanModule,
|
||||
ReverseShareModule,
|
||||
OAuthModule,
|
||||
CacheModule.register({
|
||||
isGlobal: true,
|
||||
}),
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
{
|
||||
provide: APP_GUARD,
|
||||
|
||||
@@ -47,7 +47,7 @@ export class AuthController {
|
||||
|
||||
const result = await this.authService.signUp(dto);
|
||||
|
||||
response = this.addTokensToResponse(
|
||||
this.authService.addTokensToResponse(
|
||||
response,
|
||||
result.refreshToken,
|
||||
result.accessToken,
|
||||
@@ -66,7 +66,7 @@ export class AuthController {
|
||||
const result = await this.authService.signIn(dto);
|
||||
|
||||
if (result.accessToken && result.refreshToken) {
|
||||
response = this.addTokensToResponse(
|
||||
this.authService.addTokensToResponse(
|
||||
response,
|
||||
result.refreshToken,
|
||||
result.accessToken,
|
||||
@@ -85,7 +85,7 @@ export class AuthController {
|
||||
) {
|
||||
const result = await this.authTotpService.signInTotp(dto);
|
||||
|
||||
response = this.addTokensToResponse(
|
||||
this.authService.addTokensToResponse(
|
||||
response,
|
||||
result.refreshToken,
|
||||
result.accessToken,
|
||||
@@ -117,11 +117,11 @@ export class AuthController {
|
||||
) {
|
||||
const result = await this.authService.updatePassword(
|
||||
user,
|
||||
dto.oldPassword,
|
||||
dto.password,
|
||||
dto.oldPassword,
|
||||
);
|
||||
|
||||
response = this.addTokensToResponse(response, result.refreshToken);
|
||||
this.authService.addTokensToResponse(response, result.refreshToken);
|
||||
return new TokenDTO().from(result);
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ export class AuthController {
|
||||
const accessToken = await this.authService.refreshAccessToken(
|
||||
request.cookies.refresh_token,
|
||||
);
|
||||
response = this.addTokensToResponse(response, undefined, accessToken);
|
||||
this.authService.addTokensToResponse(response, undefined, accessToken);
|
||||
return new TokenDTO().from({ accessToken });
|
||||
}
|
||||
|
||||
@@ -172,22 +172,4 @@ export class AuthController {
|
||||
// Note: We use VerifyTotpDTO here because it has both fields we need: password and totp code
|
||||
return this.authTotpService.disableTotp(user, body.password, body.code);
|
||||
}
|
||||
|
||||
private addTokensToResponse(
|
||||
response: Response,
|
||||
refreshToken?: string,
|
||||
accessToken?: string,
|
||||
) {
|
||||
if (accessToken)
|
||||
response.cookie("access_token", accessToken, { sameSite: "lax" });
|
||||
if (refreshToken)
|
||||
response.cookie("refresh_token", refreshToken, {
|
||||
path: "/api/auth/token",
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
maxAge: 1000 * 60 * 60 * 24 * 30 * 3,
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,12 @@ import { AuthTotpService } from "./authTotp.service";
|
||||
import { JwtStrategy } from "./strategy/jwt.strategy";
|
||||
|
||||
@Module({
|
||||
imports: [JwtModule.register({}), EmailModule],
|
||||
imports: [
|
||||
JwtModule.register({
|
||||
global: true,
|
||||
}),
|
||||
EmailModule,
|
||||
],
|
||||
controllers: [AuthController],
|
||||
providers: [AuthService, AuthTotpService, JwtStrategy],
|
||||
exports: [AuthService],
|
||||
|
||||
@@ -8,6 +8,7 @@ import { JwtService } from "@nestjs/jwt";
|
||||
import { User } from "@prisma/client";
|
||||
import { PrismaClientKnownRequestError } from "@prisma/client/runtime/library";
|
||||
import * as argon from "argon2";
|
||||
import { Request, Response } from "express";
|
||||
import * as moment from "moment";
|
||||
import { ConfigService } from "src/config/config.service";
|
||||
import { EmailService } from "src/email/email.service";
|
||||
@@ -27,7 +28,7 @@ export class AuthService {
|
||||
async signUp(dto: AuthRegisterDTO) {
|
||||
const isFirstUser = (await this.prisma.user.count()) == 0;
|
||||
|
||||
const hash = await argon.hash(dto.password);
|
||||
const hash = dto.password ? await argon.hash(dto.password) : null;
|
||||
try {
|
||||
const user = await this.prisma.user.create({
|
||||
data: {
|
||||
@@ -43,7 +44,7 @@ export class AuthService {
|
||||
);
|
||||
const accessToken = await this.createAccessToken(user, refreshTokenId);
|
||||
|
||||
return { accessToken, refreshToken };
|
||||
return { accessToken, refreshToken, user };
|
||||
} catch (e) {
|
||||
if (e instanceof PrismaClientKnownRequestError) {
|
||||
if (e.code == "P2002") {
|
||||
@@ -69,9 +70,16 @@ export class AuthService {
|
||||
if (!user || !(await argon.verify(user.password, dto.password)))
|
||||
throw new UnauthorizedException("Wrong email or password");
|
||||
|
||||
return this.generateToken(user);
|
||||
}
|
||||
|
||||
async generateToken(user: User, isOAuth = false) {
|
||||
// TODO: Make all old loginTokens invalid when a new one is created
|
||||
// Check if the user has TOTP enabled
|
||||
if (user.totpVerified) {
|
||||
if (
|
||||
user.totpVerified &&
|
||||
!(isOAuth && this.config.get("oauth.ignoreTotp"))
|
||||
) {
|
||||
const loginToken = await this.createLoginToken(user.id);
|
||||
|
||||
return { loginToken };
|
||||
@@ -129,9 +137,11 @@ export class AuthService {
|
||||
});
|
||||
}
|
||||
|
||||
async updatePassword(user: User, oldPassword: string, newPassword: string) {
|
||||
if (!(await argon.verify(user.password, oldPassword)))
|
||||
throw new ForbiddenException("Invalid password");
|
||||
async updatePassword(user: User, newPassword: string, oldPassword?: string) {
|
||||
const isPasswordValid =
|
||||
!user.password || !(await argon.verify(user.password, oldPassword));
|
||||
|
||||
if (!isPasswordValid) throw new ForbiddenException("Invalid password");
|
||||
|
||||
const hash = await argon.hash(newPassword);
|
||||
|
||||
@@ -210,4 +220,38 @@ export class AuthService {
|
||||
|
||||
return loginToken;
|
||||
}
|
||||
|
||||
addTokensToResponse(
|
||||
response: Response,
|
||||
refreshToken?: string,
|
||||
accessToken?: string,
|
||||
) {
|
||||
if (accessToken)
|
||||
response.cookie("access_token", accessToken, { sameSite: "lax" });
|
||||
if (refreshToken)
|
||||
response.cookie("refresh_token", refreshToken, {
|
||||
path: "/api/auth/token",
|
||||
httpOnly: true,
|
||||
sameSite: "strict",
|
||||
maxAge: 1000 * 60 * 60 * 24 * 30 * 3,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the user id if the user is logged in, null otherwise
|
||||
*/
|
||||
async getIdOfCurrentUser(request: Request): Promise<string | null> {
|
||||
if (!request.cookies.access_token) return null;
|
||||
try {
|
||||
const payload = await this.jwtService.verifyAsync(
|
||||
request.cookies.access_token,
|
||||
{
|
||||
secret: this.config.get("internal.jwtSecret"),
|
||||
},
|
||||
);
|
||||
return payload.sub;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,43 +22,29 @@ export class AuthTotpService {
|
||||
) {}
|
||||
|
||||
async signInTotp(dto: AuthSignInTotpDTO) {
|
||||
if (!dto.email && !dto.username)
|
||||
throw new BadRequestException("Email or username is required");
|
||||
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
OR: [{ email: dto.email }, { username: dto.username }],
|
||||
},
|
||||
});
|
||||
|
||||
if (!user || !(await argon.verify(user.password, dto.password)))
|
||||
throw new UnauthorizedException("Wrong email or password");
|
||||
|
||||
const token = await this.prisma.loginToken.findFirst({
|
||||
where: {
|
||||
token: dto.loginToken,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!token || token.userId != user.id || token.used)
|
||||
if (!token || token.used)
|
||||
throw new UnauthorizedException("Invalid login token");
|
||||
|
||||
if (token.expiresAt < new Date())
|
||||
throw new UnauthorizedException("Login token expired", "token_expired");
|
||||
|
||||
// Check the TOTP code
|
||||
const { totpSecret } = await this.prisma.user.findUnique({
|
||||
where: { id: user.id },
|
||||
select: { totpSecret: true },
|
||||
});
|
||||
const { totpSecret } = token.user;
|
||||
|
||||
if (!totpSecret) {
|
||||
throw new BadRequestException("TOTP is not enabled");
|
||||
}
|
||||
|
||||
const expected = authenticator.generate(totpSecret);
|
||||
|
||||
if (dto.totp !== expected) {
|
||||
if (!authenticator.check(dto.totp, totpSecret)) {
|
||||
throw new BadRequestException("Invalid code");
|
||||
}
|
||||
|
||||
@@ -69,9 +55,9 @@ export class AuthTotpService {
|
||||
});
|
||||
|
||||
const { refreshToken, refreshTokenId } =
|
||||
await this.authService.createRefreshToken(user.id);
|
||||
await this.authService.createRefreshToken(token.user.id);
|
||||
const accessToken = await this.authService.createAccessToken(
|
||||
user,
|
||||
token.user,
|
||||
refreshTokenId,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { IsString } from "class-validator";
|
||||
import { AuthSignInDTO } from "./authSignIn.dto";
|
||||
|
||||
export class AuthSignInTotpDTO extends AuthSignInDTO {
|
||||
export class AuthSignInTotpDTO {
|
||||
@IsString()
|
||||
totp: string;
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { PickType } from "@nestjs/swagger";
|
||||
import { IsString } from "class-validator";
|
||||
import { IsOptional, IsString } from "class-validator";
|
||||
import { UserDTO } from "src/user/dto/user.dto";
|
||||
|
||||
export class UpdatePasswordDTO extends PickType(UserDTO, ["password"]) {
|
||||
@IsString()
|
||||
oldPassword: string;
|
||||
@IsOptional()
|
||||
oldPassword?: string;
|
||||
}
|
||||
|
||||
@@ -6,13 +6,20 @@ import {
|
||||
} from "@nestjs/common";
|
||||
import { Config } from "@prisma/client";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { EventEmitter } from "events";
|
||||
|
||||
/**
|
||||
* ConfigService extends EventEmitter to allow listening for config updates,
|
||||
* now only `update` event will be emitted.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ConfigService {
|
||||
export class ConfigService extends EventEmitter {
|
||||
constructor(
|
||||
@Inject("CONFIG_VARIABLES") private configVariables: Config[],
|
||||
private prisma: PrismaService,
|
||||
) {}
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get(key: `${string}.${string}`): any {
|
||||
const configVariable = this.configVariables.filter(
|
||||
@@ -105,6 +112,8 @@ export class ConfigService {
|
||||
|
||||
this.configVariables = await this.prisma.config.findMany();
|
||||
|
||||
this.emit("update", key, value);
|
||||
|
||||
return updatedVariable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,8 @@ const IMAGES_PATH = "../frontend/public/img";
|
||||
@Injectable()
|
||||
export class LogoService {
|
||||
async create(file: Buffer) {
|
||||
fs.writeFileSync(`${IMAGES_PATH}/logo.png`, file, "binary");
|
||||
const resized = await sharp(file).resize(900).toBuffer();
|
||||
fs.writeFileSync(`${IMAGES_PATH}/logo.png`, resized, "binary");
|
||||
this.createFavicon(file);
|
||||
this.createPWAIcons(file);
|
||||
}
|
||||
|
||||
@@ -61,6 +61,28 @@ export class JobsService {
|
||||
}
|
||||
}
|
||||
|
||||
@Cron("0 */6 * * *")
|
||||
async deleteUnfinishedShares() {
|
||||
const unfinishedShares = await this.prisma.share.findMany({
|
||||
where: {
|
||||
createdAt: { lt: moment().subtract(1, "day").toDate() },
|
||||
uploadLocked: false,
|
||||
},
|
||||
});
|
||||
|
||||
for (const unfinishedShare of unfinishedShares) {
|
||||
await this.prisma.share.delete({
|
||||
where: { id: unfinishedShare.id },
|
||||
});
|
||||
|
||||
await this.fileService.deleteAllFiles(unfinishedShare.id);
|
||||
}
|
||||
|
||||
if (unfinishedShares.length > 0) {
|
||||
this.logger.log(`Deleted ${unfinishedShares.length} unfinished shares`);
|
||||
}
|
||||
}
|
||||
|
||||
@Cron("0 0 * * *")
|
||||
deleteTemporaryFiles() {
|
||||
let filesDeleted = 0;
|
||||
@@ -93,7 +115,7 @@ export class JobsService {
|
||||
this.logger.log(`Deleted ${filesDeleted} temporary files`);
|
||||
}
|
||||
|
||||
@Cron("0 * * * *")
|
||||
@Cron("1 * * * *")
|
||||
async deleteExpiredTokens() {
|
||||
const { count: refreshTokenCount } =
|
||||
await this.prisma.refreshToken.deleteMany({
|
||||
|
||||
9
backend/src/oauth/dto/oauthCallback.dto.ts
Normal file
9
backend/src/oauth/dto/oauthCallback.dto.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { IsString } from "class-validator";
|
||||
|
||||
export class OAuthCallbackDto {
|
||||
@IsString()
|
||||
code: string;
|
||||
|
||||
@IsString()
|
||||
state: string;
|
||||
}
|
||||
6
backend/src/oauth/dto/oauthSignIn.dto.ts
Normal file
6
backend/src/oauth/dto/oauthSignIn.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export interface OAuthSignInDto {
|
||||
provider: "github" | "google" | "microsoft" | "discord" | "oidc";
|
||||
providerId: string;
|
||||
providerUsername: string;
|
||||
email: string;
|
||||
}
|
||||
15
backend/src/oauth/exceptions/errorPage.exception.ts
Normal file
15
backend/src/oauth/exceptions/errorPage.exception.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export class ErrorPageException extends Error {
|
||||
/**
|
||||
* Exception for redirecting to error page (all i18n key should omit `error.msg` and `error.param` prefix)
|
||||
* @param key i18n key of message
|
||||
* @param redirect redirect url
|
||||
* @param params message params (key)
|
||||
*/
|
||||
constructor(
|
||||
public readonly key: string = "default",
|
||||
public readonly redirect: string = "/",
|
||||
public readonly params?: string[],
|
||||
) {
|
||||
super("error");
|
||||
}
|
||||
}
|
||||
22
backend/src/oauth/filter/errorPageException.filter.ts
Normal file
22
backend/src/oauth/filter/errorPageException.filter.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { ArgumentsHost, Catch, ExceptionFilter } from "@nestjs/common";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { ErrorPageException } from "../exceptions/errorPage.exception";
|
||||
|
||||
@Catch(ErrorPageException)
|
||||
export class ErrorPageExceptionFilter implements ExceptionFilter {
|
||||
constructor(private config: ConfigService) {}
|
||||
|
||||
catch(exception: ErrorPageException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse();
|
||||
|
||||
const url = new URL(`${this.config.get("general.appUrl")}/error`);
|
||||
url.searchParams.set("redirect", exception.redirect);
|
||||
url.searchParams.set("error", exception.key);
|
||||
if (exception.params) {
|
||||
url.searchParams.set("params", exception.params.join(","));
|
||||
}
|
||||
|
||||
response.redirect(url.toString());
|
||||
}
|
||||
}
|
||||
31
backend/src/oauth/filter/oauthException.filter.ts
Normal file
31
backend/src/oauth/filter/oauthException.filter.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
ArgumentsHost,
|
||||
Catch,
|
||||
ExceptionFilter,
|
||||
HttpException,
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
|
||||
@Catch(HttpException)
|
||||
export class OAuthExceptionFilter implements ExceptionFilter {
|
||||
private errorKeys: Record<string, string> = {
|
||||
access_denied: "access_denied",
|
||||
expired_token: "expired_token",
|
||||
};
|
||||
|
||||
constructor(private config: ConfigService) {}
|
||||
|
||||
catch(_exception: HttpException, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse();
|
||||
const request = ctx.getRequest();
|
||||
|
||||
const key = this.errorKeys[request.query.error] || "default";
|
||||
|
||||
const url = new URL(`${this.config.get("general.appUrl")}/error`);
|
||||
url.searchParams.set("redirect", "/account");
|
||||
url.searchParams.set("error", key);
|
||||
|
||||
response.redirect(url.toString());
|
||||
}
|
||||
}
|
||||
12
backend/src/oauth/guard/oauth.guard.ts
Normal file
12
backend/src/oauth/guard/oauth.guard.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class OAuthGuard implements CanActivate {
|
||||
constructor() {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const provider = request.params.provider;
|
||||
return request.query.state === request.cookies[`oauth_${provider}_state`];
|
||||
}
|
||||
}
|
||||
24
backend/src/oauth/guard/provider.guard.ts
Normal file
24
backend/src/oauth/guard/provider.guard.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import {
|
||||
CanActivate,
|
||||
ExecutionContext,
|
||||
Inject,
|
||||
Injectable,
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
|
||||
@Injectable()
|
||||
export class ProviderGuard implements CanActivate {
|
||||
constructor(
|
||||
private config: ConfigService,
|
||||
@Inject("OAUTH_PLATFORMS") private platforms: string[],
|
||||
) {}
|
||||
|
||||
canActivate(context: ExecutionContext): boolean {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const provider = request.params.provider;
|
||||
return (
|
||||
this.platforms.includes(provider) &&
|
||||
this.config.get(`oauth.${provider}-enabled`)
|
||||
);
|
||||
}
|
||||
}
|
||||
110
backend/src/oauth/oauth.controller.ts
Normal file
110
backend/src/oauth/oauth.controller.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import {
|
||||
Controller,
|
||||
Get,
|
||||
Inject,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
Req,
|
||||
Res,
|
||||
UseFilters,
|
||||
UseGuards,
|
||||
} from "@nestjs/common";
|
||||
import { User } from "@prisma/client";
|
||||
import { Request, Response } from "express";
|
||||
import { nanoid } from "nanoid";
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { GetUser } from "../auth/decorator/getUser.decorator";
|
||||
import { JwtGuard } from "../auth/guard/jwt.guard";
|
||||
import { ConfigService } from "../config/config.service";
|
||||
import { OAuthCallbackDto } from "./dto/oauthCallback.dto";
|
||||
import { ErrorPageExceptionFilter } from "./filter/errorPageException.filter";
|
||||
import { OAuthGuard } from "./guard/oauth.guard";
|
||||
import { ProviderGuard } from "./guard/provider.guard";
|
||||
import { OAuthService } from "./oauth.service";
|
||||
import { OAuthProvider } from "./provider/oauthProvider.interface";
|
||||
import { OAuthExceptionFilter } from "./filter/oauthException.filter";
|
||||
|
||||
@Controller("oauth")
|
||||
export class OAuthController {
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private oauthService: OAuthService,
|
||||
private config: ConfigService,
|
||||
@Inject("OAUTH_PROVIDERS")
|
||||
private providers: Record<string, OAuthProvider<unknown>>,
|
||||
) {}
|
||||
|
||||
@Get("available")
|
||||
available() {
|
||||
return this.oauthService.available();
|
||||
}
|
||||
|
||||
@Get("status")
|
||||
@UseGuards(JwtGuard)
|
||||
async status(@GetUser() user: User) {
|
||||
return this.oauthService.status(user);
|
||||
}
|
||||
|
||||
@Get("auth/:provider")
|
||||
@UseGuards(ProviderGuard)
|
||||
@UseFilters(ErrorPageExceptionFilter)
|
||||
async auth(
|
||||
@Param("provider") provider: string,
|
||||
@Res({ passthrough: true }) response: Response,
|
||||
) {
|
||||
const state = nanoid(16);
|
||||
const url = await this.providers[provider].getAuthEndpoint(state);
|
||||
response.cookie(`oauth_${provider}_state`, state, { sameSite: "lax" });
|
||||
response.redirect(url);
|
||||
}
|
||||
|
||||
@Get("callback/:provider")
|
||||
@UseGuards(ProviderGuard, OAuthGuard)
|
||||
@UseFilters(ErrorPageExceptionFilter, OAuthExceptionFilter)
|
||||
async callback(
|
||||
@Param("provider") provider: string,
|
||||
@Query() query: OAuthCallbackDto,
|
||||
@Req() request: Request,
|
||||
@Res({ passthrough: true }) response: Response,
|
||||
) {
|
||||
const oauthToken = await this.providers[provider].getToken(query);
|
||||
const user = await this.providers[provider].getUserInfo(oauthToken, query);
|
||||
const id = await this.authService.getIdOfCurrentUser(request);
|
||||
|
||||
if (id) {
|
||||
await this.oauthService.link(
|
||||
id,
|
||||
provider,
|
||||
user.providerId,
|
||||
user.providerUsername,
|
||||
);
|
||||
response.redirect(this.config.get("general.appUrl") + "/account");
|
||||
} else {
|
||||
const token: {
|
||||
accessToken?: string;
|
||||
refreshToken?: string;
|
||||
loginToken?: string;
|
||||
} = await this.oauthService.signIn(user);
|
||||
if (token.accessToken) {
|
||||
this.authService.addTokensToResponse(
|
||||
response,
|
||||
token.refreshToken,
|
||||
token.accessToken,
|
||||
);
|
||||
response.redirect(this.config.get("general.appUrl"));
|
||||
} else {
|
||||
response.redirect(
|
||||
this.config.get("general.appUrl") + `/auth/totp/${token.loginToken}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Post("unlink/:provider")
|
||||
@UseGuards(JwtGuard, ProviderGuard)
|
||||
@UseFilters(ErrorPageExceptionFilter)
|
||||
unlink(@GetUser() user: User, @Param("provider") provider: string) {
|
||||
return this.oauthService.unlink(user, provider);
|
||||
}
|
||||
}
|
||||
56
backend/src/oauth/oauth.module.ts
Normal file
56
backend/src/oauth/oauth.module.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import { Module } from "@nestjs/common";
|
||||
import { OAuthController } from "./oauth.controller";
|
||||
import { OAuthService } from "./oauth.service";
|
||||
import { AuthModule } from "../auth/auth.module";
|
||||
import { GitHubProvider } from "./provider/github.provider";
|
||||
import { GoogleProvider } from "./provider/google.provider";
|
||||
import { OAuthProvider } from "./provider/oauthProvider.interface";
|
||||
import { OidcProvider } from "./provider/oidc.provider";
|
||||
import { DiscordProvider } from "./provider/discord.provider";
|
||||
import { MicrosoftProvider } from "./provider/microsoft.provider";
|
||||
|
||||
@Module({
|
||||
controllers: [OAuthController],
|
||||
providers: [
|
||||
OAuthService,
|
||||
GitHubProvider,
|
||||
GoogleProvider,
|
||||
MicrosoftProvider,
|
||||
DiscordProvider,
|
||||
OidcProvider,
|
||||
{
|
||||
provide: "OAUTH_PROVIDERS",
|
||||
useFactory(
|
||||
github: GitHubProvider,
|
||||
google: GoogleProvider,
|
||||
microsoft: MicrosoftProvider,
|
||||
discord: DiscordProvider,
|
||||
oidc: OidcProvider,
|
||||
): Record<string, OAuthProvider<unknown>> {
|
||||
return {
|
||||
github,
|
||||
google,
|
||||
microsoft,
|
||||
discord,
|
||||
oidc,
|
||||
};
|
||||
},
|
||||
inject: [
|
||||
GitHubProvider,
|
||||
GoogleProvider,
|
||||
MicrosoftProvider,
|
||||
DiscordProvider,
|
||||
OidcProvider,
|
||||
],
|
||||
},
|
||||
{
|
||||
provide: "OAUTH_PLATFORMS",
|
||||
useFactory(providers: Record<string, OAuthProvider<unknown>>): string[] {
|
||||
return Object.keys(providers);
|
||||
},
|
||||
inject: ["OAUTH_PROVIDERS"],
|
||||
},
|
||||
],
|
||||
imports: [AuthModule],
|
||||
})
|
||||
export class OAuthModule {}
|
||||
171
backend/src/oauth/oauth.service.ts
Normal file
171
backend/src/oauth/oauth.service.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { User } from "@prisma/client";
|
||||
import { nanoid } from "nanoid";
|
||||
import { AuthService } from "../auth/auth.service";
|
||||
import { ConfigService } from "../config/config.service";
|
||||
import { PrismaService } from "../prisma/prisma.service";
|
||||
import { OAuthSignInDto } from "./dto/oauthSignIn.dto";
|
||||
import { ErrorPageException } from "./exceptions/errorPage.exception";
|
||||
|
||||
@Injectable()
|
||||
export class OAuthService {
|
||||
constructor(
|
||||
private prisma: PrismaService,
|
||||
private config: ConfigService,
|
||||
private auth: AuthService,
|
||||
@Inject("OAUTH_PLATFORMS") private platforms: string[],
|
||||
) {}
|
||||
|
||||
available(): string[] {
|
||||
return this.platforms
|
||||
.map((platform) => [
|
||||
platform,
|
||||
this.config.get(`oauth.${platform}-enabled`),
|
||||
])
|
||||
.filter(([_, enabled]) => enabled)
|
||||
.map(([platform, _]) => platform);
|
||||
}
|
||||
|
||||
async status(user: User) {
|
||||
const oauthUsers = await this.prisma.oAuthUser.findMany({
|
||||
select: {
|
||||
provider: true,
|
||||
providerUsername: true,
|
||||
},
|
||||
where: {
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
return Object.fromEntries(oauthUsers.map((u) => [u.provider, u]));
|
||||
}
|
||||
|
||||
async signIn(user: OAuthSignInDto) {
|
||||
const oauthUser = await this.prisma.oAuthUser.findFirst({
|
||||
where: {
|
||||
provider: user.provider,
|
||||
providerUserId: user.providerId,
|
||||
},
|
||||
include: {
|
||||
user: true,
|
||||
},
|
||||
});
|
||||
if (oauthUser) {
|
||||
return this.auth.generateToken(oauthUser.user, true);
|
||||
}
|
||||
|
||||
return this.signUp(user);
|
||||
}
|
||||
|
||||
async link(
|
||||
userId: string,
|
||||
provider: string,
|
||||
providerUserId: string,
|
||||
providerUsername: string,
|
||||
) {
|
||||
const oauthUser = await this.prisma.oAuthUser.findFirst({
|
||||
where: {
|
||||
provider,
|
||||
providerUserId,
|
||||
},
|
||||
});
|
||||
if (oauthUser) {
|
||||
throw new ErrorPageException("already_linked", "/account", [
|
||||
`provider_${provider}`,
|
||||
]);
|
||||
}
|
||||
|
||||
await this.prisma.oAuthUser.create({
|
||||
data: {
|
||||
userId,
|
||||
provider,
|
||||
providerUsername,
|
||||
providerUserId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async unlink(user: User, provider: string) {
|
||||
const oauthUser = await this.prisma.oAuthUser.findFirst({
|
||||
where: {
|
||||
userId: user.id,
|
||||
provider,
|
||||
},
|
||||
});
|
||||
if (oauthUser) {
|
||||
await this.prisma.oAuthUser.delete({
|
||||
where: {
|
||||
id: oauthUser.id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
throw new ErrorPageException("not_linked", "/account", [provider]);
|
||||
}
|
||||
}
|
||||
|
||||
private async getAvailableUsername(email: string) {
|
||||
// only remove + and - from email for now (maybe not enough)
|
||||
let username = email.split("@")[0].replace(/[+-]/g, "").substring(0, 20);
|
||||
while (true) {
|
||||
const user = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
username: username,
|
||||
},
|
||||
});
|
||||
if (user) {
|
||||
username = username + "_" + nanoid(10).replaceAll("-", "");
|
||||
} else {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async signUp(user: OAuthSignInDto) {
|
||||
// register
|
||||
if (!this.config.get("oauth.allowRegistration")) {
|
||||
throw new ErrorPageException("no_user", "/auth/signIn", [
|
||||
`provider_${user.provider}`,
|
||||
]);
|
||||
}
|
||||
|
||||
if (!user.email) {
|
||||
throw new ErrorPageException("no_email", "/auth/signIn", [
|
||||
`provider_${user.provider}`,
|
||||
]);
|
||||
}
|
||||
|
||||
const existingUser: User = await this.prisma.user.findFirst({
|
||||
where: {
|
||||
email: user.email,
|
||||
},
|
||||
});
|
||||
|
||||
if (existingUser) {
|
||||
await this.prisma.oAuthUser.create({
|
||||
data: {
|
||||
provider: user.provider,
|
||||
providerUserId: user.providerId.toString(),
|
||||
providerUsername: user.providerUsername,
|
||||
userId: existingUser.id,
|
||||
},
|
||||
});
|
||||
return this.auth.generateToken(existingUser, true);
|
||||
}
|
||||
|
||||
const result = await this.auth.signUp({
|
||||
email: user.email,
|
||||
username: await this.getAvailableUsername(user.email),
|
||||
password: null,
|
||||
});
|
||||
|
||||
await this.prisma.oAuthUser.create({
|
||||
data: {
|
||||
provider: user.provider,
|
||||
providerUserId: user.providerId.toString(),
|
||||
providerUsername: user.providerUsername,
|
||||
userId: result.user.id,
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
98
backend/src/oauth/provider/discord.provider.ts
Normal file
98
backend/src/oauth/provider/discord.provider.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
|
||||
import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
|
||||
import { OAuthSignInDto } from "../dto/oauthSignIn.dto";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import fetch from "node-fetch";
|
||||
|
||||
@Injectable()
|
||||
export class DiscordProvider implements OAuthProvider<DiscordToken> {
|
||||
constructor(private config: ConfigService) {}
|
||||
|
||||
getAuthEndpoint(state: string): Promise<string> {
|
||||
return Promise.resolve(
|
||||
"https://discord.com/api/oauth2/authorize?" +
|
||||
new URLSearchParams({
|
||||
client_id: this.config.get("oauth.discord-clientId"),
|
||||
redirect_uri:
|
||||
this.config.get("general.appUrl") + "/api/oauth/callback/discord",
|
||||
response_type: "code",
|
||||
state: state,
|
||||
scope: "identify email",
|
||||
}).toString(),
|
||||
);
|
||||
}
|
||||
|
||||
private getAuthorizationHeader() {
|
||||
return (
|
||||
"Basic " +
|
||||
Buffer.from(
|
||||
this.config.get("oauth.discord-clientId") +
|
||||
":" +
|
||||
this.config.get("oauth.discord-clientSecret"),
|
||||
).toString("base64")
|
||||
);
|
||||
}
|
||||
|
||||
async getToken(query: OAuthCallbackDto): Promise<OAuthToken<DiscordToken>> {
|
||||
const res = await fetch("https://discord.com/api/v10/oauth2/token", {
|
||||
method: "post",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
Authorization: this.getAuthorizationHeader(),
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
code: query.code,
|
||||
grant_type: "authorization_code",
|
||||
redirect_uri:
|
||||
this.config.get("general.appUrl") + "/api/oauth/callback/discord",
|
||||
}),
|
||||
});
|
||||
const token: DiscordToken = await res.json();
|
||||
return {
|
||||
accessToken: token.access_token,
|
||||
refreshToken: token.refresh_token,
|
||||
expiresIn: token.expires_in,
|
||||
scope: token.scope,
|
||||
tokenType: token.token_type,
|
||||
rawToken: token,
|
||||
};
|
||||
}
|
||||
|
||||
async getUserInfo(token: OAuthToken<DiscordToken>): Promise<OAuthSignInDto> {
|
||||
const res = await fetch("https://discord.com/api/v10/user/@me", {
|
||||
method: "post",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
Authorization: `${token.tokenType || "Bearer"} ${token.accessToken}`,
|
||||
},
|
||||
});
|
||||
const user = (await res.json()) as DiscordUser;
|
||||
if (user.verified === false) {
|
||||
throw new BadRequestException("Unverified account.");
|
||||
}
|
||||
|
||||
return {
|
||||
provider: "discord",
|
||||
providerId: user.id,
|
||||
providerUsername: user.global_name ?? user.username,
|
||||
email: user.email,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export interface DiscordToken {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
refresh_token: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
export interface DiscordUser {
|
||||
id: string;
|
||||
username: string;
|
||||
global_name: string;
|
||||
email: string;
|
||||
verified: boolean;
|
||||
}
|
||||
206
backend/src/oauth/provider/genericOidc.provider.ts
Normal file
206
backend/src/oauth/provider/genericOidc.provider.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { BadRequestException } from "@nestjs/common";
|
||||
import fetch from "node-fetch";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Cache } from "cache-manager";
|
||||
import { nanoid } from "nanoid";
|
||||
import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
|
||||
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
|
||||
import { OAuthSignInDto } from "../dto/oauthSignIn.dto";
|
||||
|
||||
export abstract class GenericOidcProvider implements OAuthProvider<OidcToken> {
|
||||
protected redirectUri: string;
|
||||
protected discoveryUri: string;
|
||||
private configuration: OidcConfigurationCache;
|
||||
private jwk: OidcJwkCache;
|
||||
|
||||
protected constructor(
|
||||
protected name: string,
|
||||
protected keyOfConfigUpdateEvents: string[],
|
||||
protected config: ConfigService,
|
||||
protected jwtService: JwtService,
|
||||
protected cache: Cache,
|
||||
) {
|
||||
this.discoveryUri = this.getDiscoveryUri();
|
||||
this.redirectUri = `${this.config.get(
|
||||
"general.appUrl",
|
||||
)}/api/oauth/callback/${this.name}`;
|
||||
this.config.addListener("update", (key: string, _: unknown) => {
|
||||
if (this.keyOfConfigUpdateEvents.includes(key)) {
|
||||
this.deinit();
|
||||
this.discoveryUri = this.getDiscoveryUri();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async getConfiguration(): Promise<OidcConfiguration> {
|
||||
if (!this.configuration || this.configuration.expires < Date.now()) {
|
||||
await this.fetchConfiguration();
|
||||
}
|
||||
return this.configuration.data;
|
||||
}
|
||||
|
||||
async getJwk(): Promise<OidcJwk[]> {
|
||||
if (!this.jwk || this.jwk.expires < Date.now()) {
|
||||
await this.fetchJwk();
|
||||
}
|
||||
return this.jwk.data;
|
||||
}
|
||||
|
||||
async getAuthEndpoint(state: string) {
|
||||
const configuration = await this.getConfiguration();
|
||||
const endpoint = configuration.authorization_endpoint;
|
||||
|
||||
const nonce = nanoid();
|
||||
await this.cache.set(
|
||||
`oauth-${this.name}-nonce-${state}`,
|
||||
nonce,
|
||||
1000 * 60 * 5,
|
||||
);
|
||||
|
||||
return (
|
||||
endpoint +
|
||||
"?" +
|
||||
new URLSearchParams({
|
||||
client_id: this.config.get(`oauth.${this.name}-clientId`),
|
||||
response_type: "code",
|
||||
scope: "openid profile email",
|
||||
redirect_uri: this.redirectUri,
|
||||
state,
|
||||
nonce,
|
||||
}).toString()
|
||||
);
|
||||
}
|
||||
|
||||
async getToken(query: OAuthCallbackDto): Promise<OAuthToken<OidcToken>> {
|
||||
const configuration = await this.getConfiguration();
|
||||
const endpoint = configuration.token_endpoint;
|
||||
const res = await fetch(endpoint, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({
|
||||
client_id: this.config.get(`oauth.${this.name}-clientId`),
|
||||
client_secret: this.config.get(`oauth.${this.name}-clientSecret`),
|
||||
grant_type: "authorization_code",
|
||||
code: query.code,
|
||||
redirect_uri: this.redirectUri,
|
||||
}).toString(),
|
||||
});
|
||||
const token: OidcToken = await res.json();
|
||||
return {
|
||||
accessToken: token.access_token,
|
||||
expiresIn: token.expires_in,
|
||||
idToken: token.id_token,
|
||||
refreshToken: token.refresh_token,
|
||||
tokenType: token.token_type,
|
||||
rawToken: token,
|
||||
};
|
||||
}
|
||||
|
||||
async getUserInfo(
|
||||
token: OAuthToken<OidcToken>,
|
||||
query: OAuthCallbackDto,
|
||||
): Promise<OAuthSignInDto> {
|
||||
const idTokenData = this.decodeIdToken(token.idToken);
|
||||
// maybe it's not necessary to verify the id token since it's directly obtained from the provider
|
||||
|
||||
const key = `oauth-${this.name}-nonce-${query.state}`;
|
||||
const nonce = await this.cache.get(key);
|
||||
await this.cache.del(key);
|
||||
if (nonce !== idTokenData.nonce) {
|
||||
throw new BadRequestException("Invalid token");
|
||||
}
|
||||
|
||||
return {
|
||||
provider: this.name as any,
|
||||
email: idTokenData.email,
|
||||
providerId: idTokenData.sub,
|
||||
providerUsername: idTokenData.name,
|
||||
};
|
||||
}
|
||||
|
||||
protected abstract getDiscoveryUri(): string;
|
||||
|
||||
private async fetchConfiguration(): Promise<void> {
|
||||
const res = await fetch(this.discoveryUri);
|
||||
const expires = res.headers.has("expires")
|
||||
? new Date(res.headers.get("expires")).getTime()
|
||||
: Date.now() + 1000 * 60 * 60 * 24;
|
||||
this.configuration = {
|
||||
expires,
|
||||
data: await res.json(),
|
||||
};
|
||||
}
|
||||
|
||||
private async fetchJwk(): Promise<void> {
|
||||
const configuration = await this.getConfiguration();
|
||||
const res = await fetch(configuration.jwks_uri);
|
||||
const expires = res.headers.has("expires")
|
||||
? new Date(res.headers.get("expires")).getTime()
|
||||
: Date.now() + 1000 * 60 * 60 * 24;
|
||||
this.jwk = {
|
||||
expires,
|
||||
data: (await res.json())["keys"],
|
||||
};
|
||||
}
|
||||
|
||||
private deinit() {
|
||||
this.discoveryUri = undefined;
|
||||
this.configuration = undefined;
|
||||
this.jwk = undefined;
|
||||
}
|
||||
|
||||
private decodeIdToken(idToken: string): OidcIdToken {
|
||||
return this.jwtService.decode(idToken) as OidcIdToken;
|
||||
}
|
||||
}
|
||||
|
||||
export interface OidcCache<T> {
|
||||
expires: number;
|
||||
data: T;
|
||||
}
|
||||
|
||||
export interface OidcConfiguration {
|
||||
issuer: string;
|
||||
authorization_endpoint: string;
|
||||
token_endpoint: string;
|
||||
userinfo_endpoint?: string;
|
||||
jwks_uri: string;
|
||||
response_types_supported: string[];
|
||||
id_token_signing_alg_values_supported: string[];
|
||||
scopes_supported?: string[];
|
||||
claims_supported?: string[];
|
||||
}
|
||||
|
||||
export interface OidcJwk {
|
||||
e: string;
|
||||
alg: string;
|
||||
kid: string;
|
||||
use: string;
|
||||
kty: string;
|
||||
n: string;
|
||||
}
|
||||
|
||||
export type OidcConfigurationCache = OidcCache<OidcConfiguration>;
|
||||
|
||||
export type OidcJwkCache = OidcCache<OidcJwk[]>;
|
||||
|
||||
export interface OidcToken {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
token_type: string;
|
||||
expires_in: number;
|
||||
id_token: string;
|
||||
}
|
||||
|
||||
export interface OidcIdToken {
|
||||
iss: string;
|
||||
sub: string;
|
||||
exp: number;
|
||||
iat: number;
|
||||
email: string;
|
||||
name: string;
|
||||
nonce: string;
|
||||
}
|
||||
110
backend/src/oauth/provider/github.provider.ts
Normal file
110
backend/src/oauth/provider/github.provider.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import { OAuthProvider, OAuthToken } from "./oauthProvider.interface";
|
||||
import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
|
||||
import { OAuthSignInDto } from "../dto/oauthSignIn.dto";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import fetch from "node-fetch";
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class GitHubProvider implements OAuthProvider<GitHubToken> {
|
||||
constructor(private config: ConfigService) {}
|
||||
|
||||
getAuthEndpoint(state: string): Promise<string> {
|
||||
return Promise.resolve(
|
||||
"https://github.com/login/oauth/authorize?" +
|
||||
new URLSearchParams({
|
||||
client_id: this.config.get("oauth.github-clientId"),
|
||||
redirect_uri:
|
||||
this.config.get("general.appUrl") + "/api/oauth/callback/github",
|
||||
state: state,
|
||||
scope: "user:email",
|
||||
}).toString(),
|
||||
);
|
||||
}
|
||||
|
||||
async getToken(query: OAuthCallbackDto): Promise<OAuthToken<GitHubToken>> {
|
||||
const res = await fetch(
|
||||
"https://github.com/login/oauth/access_token?" +
|
||||
new URLSearchParams({
|
||||
client_id: this.config.get("oauth.github-clientId"),
|
||||
client_secret: this.config.get("oauth.github-clientSecret"),
|
||||
code: query.code,
|
||||
}).toString(),
|
||||
{
|
||||
method: "post",
|
||||
headers: {
|
||||
Accept: "application/json",
|
||||
},
|
||||
},
|
||||
);
|
||||
const token: GitHubToken = await res.json();
|
||||
return {
|
||||
accessToken: token.access_token,
|
||||
tokenType: token.token_type,
|
||||
rawToken: token,
|
||||
};
|
||||
}
|
||||
|
||||
async getUserInfo(token: OAuthToken<GitHubToken>): Promise<OAuthSignInDto> {
|
||||
const user = await this.getGitHubUser(token);
|
||||
if (!token.scope.includes("user:email")) {
|
||||
throw new BadRequestException("No email permission granted");
|
||||
}
|
||||
const email = await this.getGitHubEmail(token);
|
||||
if (!email) {
|
||||
throw new BadRequestException("No email found");
|
||||
}
|
||||
|
||||
return {
|
||||
provider: "github",
|
||||
providerId: user.id.toString(),
|
||||
providerUsername: user.name ?? user.login,
|
||||
email,
|
||||
};
|
||||
}
|
||||
|
||||
private async getGitHubUser(
|
||||
token: OAuthToken<GitHubToken>,
|
||||
): Promise<GitHubUser> {
|
||||
const res = await fetch("https://api.github.com/user", {
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `${token.tokenType ?? "Bearer"} ${token.accessToken}`,
|
||||
},
|
||||
});
|
||||
return (await res.json()) as GitHubUser;
|
||||
}
|
||||
|
||||
private async getGitHubEmail(
|
||||
token: OAuthToken<GitHubToken>,
|
||||
): Promise<string | undefined> {
|
||||
const res = await fetch("https://api.github.com/user/public_emails", {
|
||||
headers: {
|
||||
Accept: "application/vnd.github+json",
|
||||
Authorization: `${token.tokenType ?? "Bearer"} ${token.accessToken}`,
|
||||
},
|
||||
});
|
||||
const emails = (await res.json()) as GitHubEmail[];
|
||||
return emails.find((e) => e.primary && e.verified)?.email;
|
||||
}
|
||||
}
|
||||
|
||||
export interface GitHubToken {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
export interface GitHubUser {
|
||||
login: string;
|
||||
id: number;
|
||||
name?: string;
|
||||
email?: string; // this filed seems only return null
|
||||
}
|
||||
|
||||
export interface GitHubEmail {
|
||||
email: string;
|
||||
primary: boolean;
|
||||
verified: boolean;
|
||||
visibility: string | null;
|
||||
}
|
||||
21
backend/src/oauth/provider/google.provider.ts
Normal file
21
backend/src/oauth/provider/google.provider.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { GenericOidcProvider } from "./genericOidc.provider";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { CACHE_MANAGER } from "@nestjs/cache-manager";
|
||||
import { Cache } from "cache-manager";
|
||||
|
||||
@Injectable()
|
||||
export class GoogleProvider extends GenericOidcProvider {
|
||||
constructor(
|
||||
config: ConfigService,
|
||||
jwtService: JwtService,
|
||||
@Inject(CACHE_MANAGER) cache: Cache,
|
||||
) {
|
||||
super("google", ["oauth.google-enabled"], config, jwtService, cache);
|
||||
}
|
||||
|
||||
protected getDiscoveryUri(): string {
|
||||
return "https://accounts.google.com/.well-known/openid-configuration";
|
||||
}
|
||||
}
|
||||
29
backend/src/oauth/provider/microsoft.provider.ts
Normal file
29
backend/src/oauth/provider/microsoft.provider.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { GenericOidcProvider } from "./genericOidc.provider";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { CACHE_MANAGER } from "@nestjs/cache-manager";
|
||||
import { Cache } from "cache-manager";
|
||||
|
||||
@Injectable()
|
||||
export class MicrosoftProvider extends GenericOidcProvider {
|
||||
constructor(
|
||||
config: ConfigService,
|
||||
jwtService: JwtService,
|
||||
@Inject(CACHE_MANAGER) cache: Cache,
|
||||
) {
|
||||
super(
|
||||
"microsoft",
|
||||
["oauth.microsoft-enabled", "oauth.microsoft-tenant"],
|
||||
config,
|
||||
jwtService,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
|
||||
protected getDiscoveryUri(): string {
|
||||
return `https://login.microsoftonline.com/${this.config.get(
|
||||
"oauth.microsoft-tenant",
|
||||
)}/v2.0/.well-known/openid-configuration`;
|
||||
}
|
||||
}
|
||||
24
backend/src/oauth/provider/oauthProvider.interface.ts
Normal file
24
backend/src/oauth/provider/oauthProvider.interface.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { OAuthCallbackDto } from "../dto/oauthCallback.dto";
|
||||
import { OAuthSignInDto } from "../dto/oauthSignIn.dto";
|
||||
|
||||
/**
|
||||
* @typeParam T - type of token
|
||||
* @typeParam C - type of callback query
|
||||
*/
|
||||
export interface OAuthProvider<T, C = OAuthCallbackDto> {
|
||||
getAuthEndpoint(state: string): Promise<string>;
|
||||
|
||||
getToken(query: C): Promise<OAuthToken<T>>;
|
||||
|
||||
getUserInfo(token: OAuthToken<T>, query: C): Promise<OAuthSignInDto>;
|
||||
}
|
||||
|
||||
export interface OAuthToken<T> {
|
||||
accessToken: string;
|
||||
expiresIn?: number;
|
||||
refreshToken?: string;
|
||||
tokenType?: string;
|
||||
scope?: string;
|
||||
idToken?: string;
|
||||
rawToken: T;
|
||||
}
|
||||
27
backend/src/oauth/provider/oidc.provider.ts
Normal file
27
backend/src/oauth/provider/oidc.provider.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { GenericOidcProvider } from "./genericOidc.provider";
|
||||
import { Inject, Injectable } from "@nestjs/common";
|
||||
import { ConfigService } from "../../config/config.service";
|
||||
import { JwtService } from "@nestjs/jwt";
|
||||
import { CACHE_MANAGER } from "@nestjs/cache-manager";
|
||||
import { Cache } from "cache-manager";
|
||||
|
||||
@Injectable()
|
||||
export class OidcProvider extends GenericOidcProvider {
|
||||
constructor(
|
||||
config: ConfigService,
|
||||
jwtService: JwtService,
|
||||
@Inject(CACHE_MANAGER) protected cache: Cache,
|
||||
) {
|
||||
super(
|
||||
"oidc",
|
||||
["oauth.oidc-enabled", "oauth.oidc-discoveryUri"],
|
||||
config,
|
||||
jwtService,
|
||||
cache,
|
||||
);
|
||||
}
|
||||
|
||||
protected getDiscoveryUri(): string {
|
||||
return this.config.get("oauth.oidc-discoveryUri");
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@ export class UserDTO {
|
||||
@IsEmail()
|
||||
email: string;
|
||||
|
||||
@Expose()
|
||||
hasPassword: boolean;
|
||||
|
||||
@MinLength(8)
|
||||
password: string;
|
||||
|
||||
|
||||
@@ -28,7 +28,9 @@ export class UserController {
|
||||
@Get("me")
|
||||
@UseGuards(JwtGuard)
|
||||
async getCurrentUser(@GetUser() user: User) {
|
||||
return new UserDTO().from(user);
|
||||
const userDTO = new UserDTO().from(user);
|
||||
userDTO.hasPassword = !!user.password;
|
||||
return userDTO;
|
||||
}
|
||||
|
||||
@Patch("me")
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "es2017",
|
||||
"target": "es2021",
|
||||
"lib": [
|
||||
"ES2021"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
|
||||
_Leer esto en otro idioma: [Inglés](/README.md), [Español](/docs/README.es.md), [Chino Simplificado](/docs/README.zh-cn.md)_
|
||||
_Leer esto en otro idioma: [Inglés](/README.md), [Español](/docs/README.es.md), [Chino Simplificado](/docs/README.zh-cn.md), [日本語](/docs/README.ja-jp.md)_
|
||||
|
||||
---
|
||||
|
||||
|
||||
158
docs/README.ja-jp.md
Normal file
158
docs/README.ja-jp.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# <div align="center"><img src="https://user-images.githubusercontent.com/58886915/166198400-c2134044-1198-4647-a8b6-da9c4a204c68.svg" width="40"/> </br>Pingvin Share</div>
|
||||
|
||||
---
|
||||
|
||||
_READMEを別の言語で読む: [Spanish](/docs/README.es.md), [English](/README.md), [Simplified Chinese](/docs/README.zh-cn.md), [日本語](/docs/README.ja-jp.md)_
|
||||
|
||||
---
|
||||
|
||||
Pingvin Share は、セルフホスト型のファイル共有プラットフォームであり、WeTransfer、ギガファイル便などの代替プラットフォームです。
|
||||
|
||||
## ✨ 特徴的な機能
|
||||
|
||||
- リンクを用いたファイル共有
|
||||
- ファイルサイズ無制限 (ストレージスペースの範囲内で)
|
||||
- 共有への有効期限の設定
|
||||
- 訪問回数の制限とパスワードの設定により共有を安全に保つ
|
||||
- メールでリンクを共有
|
||||
- ClamAVと連携して、ウイルスチェックが可能
|
||||
|
||||
## 🐧 Pingvin Shareについて知る
|
||||
|
||||
- [デモ](https://pingvin-share.dev.eliasschneider.com)
|
||||
- [DB Techによるレビュー](https://www.youtube.com/watch?v=rWwNeZCOPJA)
|
||||
|
||||
<img src="https://user-images.githubusercontent.com/58886915/225038319-b2ef742c-3a74-4eb6-9689-4207a36842a4.png" width="700"/>
|
||||
|
||||
## ⌨️ セットアップ
|
||||
|
||||
> 注意: Pingvin Shareは、早期段階であり、バグが含まれている場合があります。
|
||||
|
||||
### Dockerでインストール (おすすめ)
|
||||
|
||||
1. `docker-compose.yml`ファイルをダウンロード
|
||||
2. `docker-compose up -d`を実行
|
||||
|
||||
Webサイトは、`http://localhost:3000`でリッスンされます。これでPingvin Shareをお使い頂けます🐧!
|
||||
|
||||
### スタンドアローンインストール
|
||||
|
||||
必要なツール:
|
||||
|
||||
- [Node.js](https://nodejs.org/en/download/) >= 16
|
||||
- [Git](https://git-scm.com/downloads)
|
||||
- [pm2](https://pm2.keymetrics.io/) Pingvin Shareをバックグラウンドで動作させるために必要
|
||||
|
||||
```bash
|
||||
git clone https://github.com/stonith404/pingvin-share
|
||||
cd pingvin-share
|
||||
|
||||
# 最新バージョンをチェックアウト
|
||||
git fetch --tags && git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
# バックエンドを開始
|
||||
cd backend
|
||||
npm install
|
||||
npm run build
|
||||
pm2 start --name="pingvin-share-backend" npm -- run prod
|
||||
|
||||
#フロントエンドを開始
|
||||
cd ../frontend
|
||||
npm install
|
||||
npm run build
|
||||
pm2 start --name="pingvin-share-frontend" npm -- run start
|
||||
```
|
||||
|
||||
Webサイトは、`http://localhost:3000`でリッスンされます。これでPingvin Shareをお使い頂けます🐧!
|
||||
|
||||
### 連携機能
|
||||
|
||||
#### ClamAV (Dockerのみ)
|
||||
|
||||
ClamAVは、共有されたファイルをスキャンし、感染したファイルを見つけた場合に削除するために使用されます。
|
||||
|
||||
1. ClamAVコンテナをDocker Composeの定義ファイル(`docker-compose.yml`を確認)に追加し、コンテナを開始してください。
|
||||
2. Dockerは、Pingvin Shareを開始する前に、ClamAVの準備が整うまで待機します。これには、1分から2分ほどかかります。
|
||||
3. Pingvin Shareのログに"ClamAV is active"というログが記録されます。
|
||||
|
||||
ClamAVは、非常に多くのリソースを必要とします、詳しくは[リソース](https://docs.clamav.net/manual/Installing/Docker.html#memory-ram-requirements)をご確認ください。
|
||||
|
||||
### 追加情報
|
||||
|
||||
- [Synology NASへのインストール方法](https://mariushosting.com/how-to-install-pingvin-share-on-your-synology-nas/)
|
||||
|
||||
### 新しいバージョンへのアップグレード
|
||||
|
||||
Pingvin Shareは早期段階のため、アップグレード前に必ずリリースノートを確認して、アップグレードしても問題ないかどうかご確認ください。
|
||||
|
||||
#### Docker
|
||||
|
||||
```bash
|
||||
docker compose pull
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
#### スタンドアローン
|
||||
|
||||
1. アプリを停止する
|
||||
```bash
|
||||
pm2 stop pingvin-share-backend pingvin-share-frontend
|
||||
```
|
||||
2. `git clone`のステップを除いて、[インストールガイド](#stand-alone-installation)をくり返してください。
|
||||
|
||||
```bash
|
||||
cd pingvin-share
|
||||
|
||||
# 最新バージョンをチェックアウト
|
||||
git fetch --tags && git checkout $(git describe --tags `git rev-list --tags --max-count=1`)
|
||||
|
||||
# バックエンドを開始
|
||||
cd backend
|
||||
npm run build
|
||||
pm2 restart pingvin-share-backend
|
||||
|
||||
#フロントエンドを開始
|
||||
cd ../frontend
|
||||
npm run build
|
||||
pm2 restart pingvin-share-frontend
|
||||
```
|
||||
|
||||
### 設定
|
||||
|
||||
管理者のダッシュボード内の「設定」ページから、Pingvin Shareをカスタマイズできます。
|
||||
|
||||
#### 環境変数
|
||||
|
||||
インストール時の特定の設定で、環境変数を使用できます。次の環境変数が使用可能です:
|
||||
|
||||
##### バックエンド
|
||||
|
||||
| 変数名 | デフォルト値 | 説明 |
|
||||
| ---------------- | -------------------------------------------------- | -------------------------------------- |
|
||||
| `PORT` | `8080` | バックエンドがリッスンするポート番号 |
|
||||
| `DATABASE_URL` | `file:../data/pingvin-share.db?connection_limit=1` | SQLiteのURL |
|
||||
| `DATA_DIRECTORY` | `./data` | データを保管するディレクトリ |
|
||||
| `CLAMAV_HOST` | `127.0.0.1` | ClamAVサーバーのIPアドレス |
|
||||
| `CLAMAV_PORT` | `3310` | ClamAVサーバーのポート番号 |
|
||||
|
||||
##### フロントエンド
|
||||
|
||||
| 変数名 | デフォルト値 | 説明 |
|
||||
| --------- | ----------------------- | ---------------------------------------- |
|
||||
| `PORT` | `3000` | フロントエンドがリッスンするポート番号 |
|
||||
| `API_URL` | `http://localhost:8080` | フロントエンドからアクセスするバックエンドへのURL |
|
||||
|
||||
## 🖤 コントリビュート
|
||||
|
||||
### 翻訳
|
||||
|
||||
Pingvin Shareをあなたが使用している言語に翻訳するお手伝いを募集しています。
|
||||
[Crowdin](https://crowdin.com/project/pingvin-share)上で、簡単にPingvin Shareの翻訳作業への参加が可能です。
|
||||
|
||||
あなたの言語がありませんか? 気軽に[リクエスト](https://github.com/stonith404/pingvin-share/issues/new?assignees=&labels=language-request&projects=&template=language-request.yml&title=%F0%9F%8C%90+Language+request%3A+%3Clanguage+name+in+english%3E)してください。
|
||||
|
||||
翻訳中に問題がありましたか? [ローカライズに関するディスカッション](https://github.com/stonith404/pingvin-share/discussions/198)に是非参加してください。
|
||||
|
||||
### プロジェクト
|
||||
|
||||
Pingvin Shareへのコントリビュートをいつでもお待ちしています! [コントリビューションガイド](/CONTRIBUTING.md)を確認して、是非参加してください。
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
---
|
||||
|
||||
_选择合适的语言阅读: [西班牙语](/docs/README.es.md), [英语](/README.md), [简体中文](/docs/README.zh-cn.md)_
|
||||
_选择合适的语言阅读: [西班牙语](/docs/README.es.md), [英语](/README.md), [简体中文](/docs/README.zh-cn.md), [日本语](/docs/README.ja-jp.md)_
|
||||
|
||||
---
|
||||
|
||||
|
||||
168
docs/oauth2-guide.md
Normal file
168
docs/oauth2-guide.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# OAuth 2 Login Guide
|
||||
|
||||
## Config Built-in OAuth 2 Providers
|
||||
|
||||
- [GitHub](#github)
|
||||
- [Google](#google)
|
||||
- [Microsoft](#microsoft)
|
||||
- [Discord](#discord)
|
||||
- [OpenID Connect](#openid-connect)
|
||||
|
||||
### GitHub
|
||||
|
||||
Please follow the [official guide](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
|
||||
to create an OAuth app.
|
||||
|
||||
Redirect URL: `https://<your-domain>/api/oauth/callback/github`
|
||||
|
||||
### Google
|
||||
|
||||
Please follow the [official guide](https://developers.google.com/identity/protocols/oauth2/web-server#prerequisites) to
|
||||
create an OAuth 2.0 App.
|
||||
|
||||
Redirect URL: `https://<your-domain>/api/oauth/callback/google`
|
||||
|
||||
### Microsoft
|
||||
|
||||
Please follow
|
||||
the [official guide](https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-register-app)
|
||||
to register an application.
|
||||
|
||||
Redirect URL: `https://<your-domain>/api/oauth/callback/microsoft`
|
||||
|
||||
### Discord
|
||||
|
||||
Create an application on [Discord Developer Portal](https://discord.com/developers/applications).
|
||||
|
||||
Redirect URL: `https://<your-domain>/api/oauth/callback/discord`
|
||||
|
||||
### OpenID Connect
|
||||
|
||||
Generic OpenID Connect provider is also supported, we have tested it on Keycloak and Authentik.
|
||||
|
||||
Redirect URL: `https://<your-domain>/api/oauth/callback/oidc`
|
||||
|
||||
## Custom your OAuth 2 Provider
|
||||
|
||||
If our built-in providers don't meet your needs, you can create your own OAuth 2 provider.
|
||||
|
||||
### 1. Create config
|
||||
|
||||
Add your config (client id, client secret, etc.) in [`config.seed.ts`](../backend/prisma/seed/config.seed.ts):
|
||||
|
||||
```ts
|
||||
const configVariables: ConfigVariables = {
|
||||
// ...
|
||||
oauth: {
|
||||
// ...
|
||||
"YOUR_PROVIDER_NAME-enabled": {
|
||||
type: "boolean",
|
||||
defaultValue: "false",
|
||||
},
|
||||
"YOUR_PROVIDER_NAME-clientId": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
},
|
||||
"YOUR_PROVIDER_NAME-clientSecret": {
|
||||
type: "string",
|
||||
defaultValue: "",
|
||||
obscured: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create provider class
|
||||
|
||||
#### OpenID Connect
|
||||
|
||||
If your provider supports OpenID connect, it's extremely easy to
|
||||
extend [`GenericOidcProvider`](../backend/src/oauth/provider/genericOidc.provider.ts) to add a new OpenID Connect
|
||||
provider.
|
||||
|
||||
The [Google provider](../backend/src/oauth/provider/google.provider.ts)
|
||||
and [Microsoft provider](../backend/src/oauth/provider/microsoft.provider.ts) are good examples.
|
||||
|
||||
Here are some discovery URIs for popular providers:
|
||||
|
||||
- Microsoft: `https://login.microsoftonline.com/{tenant}/v2.0/.well-known/openid-configuration`
|
||||
- Google: `https://accounts.google.com/.well-known/openid-configuration`
|
||||
- Apple: `https://appleid.apple.com/.well-known/openid-configuration`
|
||||
- Gitlab: `https://gitlab.com/.well-known/openid-configuration`
|
||||
- Huawei: `https://oauth-login.cloud.huawei.com/.well-known/openid-configuration`
|
||||
- Paypal: `https://www.paypal.com/.well-known/openid-configuration`
|
||||
- Yahoo: `https://api.login.yahoo.com/.well-known/openid-configuration`
|
||||
|
||||
#### OAuth 2
|
||||
|
||||
If your provider only supports OAuth 2, you can
|
||||
implement [`OAuthProvider`](../backend/src/oauth/provider/oauthProvider.interface.ts) interface to add a new OAuth 2
|
||||
provider.
|
||||
|
||||
The [GitHub provider](../backend/src/oauth/provider/github.provider.ts)
|
||||
and [Discord provider](../backend/src/oauth/provider/discord.provider.ts) are good examples.
|
||||
|
||||
### 3. Register provider
|
||||
|
||||
Register your provider in [`OAuthModule`](../backend/src/oauth/oauth.module.ts)
|
||||
and [`OAuthSignInDto`](../backend/src/oauth/dto/oauthSignIn.dto.ts):
|
||||
|
||||
```ts
|
||||
@Module({
|
||||
providers: [
|
||||
GitHubProvider,
|
||||
// your provider
|
||||
{
|
||||
provide: "OAUTH_PROVIDERS",
|
||||
useFactory(github: GitHubProvider, /* your provider */): Record<string, OAuthProvider<unknown>> {
|
||||
return {
|
||||
github,
|
||||
google,
|
||||
oidc,
|
||||
};
|
||||
},
|
||||
inject: [GitHubProvider, /* your provider */],
|
||||
},
|
||||
],
|
||||
})
|
||||
export class OAuthModule {
|
||||
}
|
||||
```
|
||||
|
||||
```ts
|
||||
export interface OAuthSignInDto {
|
||||
provider: 'github' | 'google' | 'microsoft' | 'discord' | 'oidc' /* your provider*/
|
||||
;
|
||||
providerId: string;
|
||||
providerUsername: string;
|
||||
email: string;
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Add frontend icon
|
||||
|
||||
Add an icon in [`oauth.util.tsx`](../frontend/src/utils/oauth.util.tsx).
|
||||
|
||||
```tsx
|
||||
const getOAuthIcon = (provider: string) => {
|
||||
return {
|
||||
'github': <SiGithub />,
|
||||
/* your provider */
|
||||
}[provider];
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Add i18n text
|
||||
|
||||
Add keys below to your i18n text in [locale file](../frontend/src/i18n/translations/en-US.ts).
|
||||
|
||||
- `signIn.oauth.YOUR_PROVIDER_NAME`
|
||||
- `account.card.oauth.YOUR_PROVIDER_NAME`
|
||||
- `admin.config.oauth.YOUR_PROVIDER_NAME-enabled`
|
||||
- `admin.config.oauth.YOUR_PROVIDER_NAME-client-id`
|
||||
- `admin.config.oauth.YOUR_PROVIDER_NAME-client-secret`
|
||||
- Other config keys you defined in step 1
|
||||
|
||||
Congratulations! 🎉 You have successfully added a new OAuth 2 provider! Pull requests are welcome if you want to share
|
||||
your provider with others.
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"react-hooks/exhaustive-deps": ["off"],
|
||||
"import/no-anonymous-default-export": ["off"],
|
||||
"no-unused-vars": ["warn"],
|
||||
"react/no-unescaped-entities": ["off"]
|
||||
"react/no-unescaped-entities": ["off"],
|
||||
"@next/next/no-img-element": ["off"]
|
||||
}
|
||||
}
|
||||
|
||||
4
frontend/package-lock.json
generated
4
frontend/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"dependencies": {
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/server": "^11.11.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share-frontend",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"scripts": {
|
||||
"dev": "next dev",
|
||||
"build": "next build",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 86 KiB |
@@ -1,6 +1,4 @@
|
||||
import Image from "next/image";
|
||||
|
||||
const Logo = ({ height, width }: { height: number; width: number }) => {
|
||||
return <Image src="/img/logo.png" alt="logo" height={height} width={width} />;
|
||||
return <img src="/img/logo.png" alt="logo" height={height} width={width} />;
|
||||
};
|
||||
export default Logo;
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from "@mantine/core";
|
||||
import Link from "next/link";
|
||||
import { Dispatch, SetStateAction } from "react";
|
||||
import { TbAt, TbMail, TbShare, TbSquare } from "react-icons/tb";
|
||||
import { TbAt, TbMail, TbShare, TbSocial, TbSquare } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const categories = [
|
||||
@@ -19,6 +19,7 @@ const categories = [
|
||||
{ name: "Email", icon: <TbMail /> },
|
||||
{ name: "Share", icon: <TbShare /> },
|
||||
{ name: "SMTP", icon: <TbAt /> },
|
||||
{ name: "OAuth", icon: <TbSocial /> },
|
||||
];
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
|
||||
@@ -2,9 +2,11 @@ import {
|
||||
Anchor,
|
||||
Button,
|
||||
Container,
|
||||
createStyles,
|
||||
Group,
|
||||
Paper,
|
||||
PasswordInput,
|
||||
Stack,
|
||||
Text,
|
||||
TextInput,
|
||||
Title,
|
||||
@@ -18,19 +20,47 @@ import { TbInfoCircle } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import authService from "../../services/auth.service";
|
||||
import { getOAuthIcon, getOAuthUrl } from "../../utils/oauth.util";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
||||
const useStyles = createStyles((theme) => ({
|
||||
or: {
|
||||
"&:before": {
|
||||
content: "''",
|
||||
flex: 1,
|
||||
display: "block",
|
||||
borderTopWidth: 1,
|
||||
borderTopStyle: "solid",
|
||||
borderColor:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[3]
|
||||
: theme.colors.gray[4],
|
||||
},
|
||||
"&:after": {
|
||||
content: "''",
|
||||
flex: 1,
|
||||
display: "block",
|
||||
borderTopWidth: 1,
|
||||
borderTopStyle: "solid",
|
||||
borderColor:
|
||||
theme.colorScheme === "dark"
|
||||
? theme.colors.dark[3]
|
||||
: theme.colors.gray[4],
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
||||
const config = useConfig();
|
||||
const router = useRouter();
|
||||
const t = useTranslate();
|
||||
const { refreshUser } = useUser();
|
||||
const { classes } = useStyles();
|
||||
|
||||
const [showTotp, setShowTotp] = React.useState(false);
|
||||
const [loginToken, setLoginToken] = React.useState("");
|
||||
const [oauth, setOAuth] = React.useState<string[]>([]);
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
emailOrUsername: yup.string().required(t("common.error.field-required")),
|
||||
@@ -44,7 +74,6 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
||||
initialValues: {
|
||||
emailOrUsername: "",
|
||||
password: "",
|
||||
totp: "",
|
||||
},
|
||||
validate: yupResolver(validationSchema),
|
||||
});
|
||||
@@ -55,7 +84,6 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
||||
.then(async (response) => {
|
||||
if (response.data["loginToken"]) {
|
||||
// Prompt the user to enter their totp code
|
||||
setShowTotp(true);
|
||||
showNotification({
|
||||
icon: <TbInfoCircle />,
|
||||
color: "blue",
|
||||
@@ -63,7 +91,11 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
||||
title: t("signIn.notify.totp-required.title"),
|
||||
message: t("signIn.notify.totp-required.description"),
|
||||
});
|
||||
setLoginToken(response.data["loginToken"]);
|
||||
router.push(
|
||||
`/auth/totp/${
|
||||
response.data["loginToken"]
|
||||
}?redirect=${encodeURIComponent(redirectPath)}`,
|
||||
);
|
||||
} else {
|
||||
await refreshUser();
|
||||
router.replace(redirectPath);
|
||||
@@ -72,25 +104,15 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
||||
.catch(toast.axiosError);
|
||||
};
|
||||
|
||||
const signInTotp = (email: string, password: string, totp: string) => {
|
||||
authService
|
||||
.signInTotp(email, password, totp, loginToken)
|
||||
.then(async () => {
|
||||
await refreshUser();
|
||||
router.replace(redirectPath);
|
||||
})
|
||||
.catch((error) => {
|
||||
if (error?.response?.data?.error == "share_password_required") {
|
||||
toast.axiosError(error);
|
||||
// Refresh the page to start over
|
||||
window.location.reload();
|
||||
}
|
||||
|
||||
toast.axiosError(error);
|
||||
form.setValues({ totp: "" });
|
||||
});
|
||||
const getAvailableOAuth = async () => {
|
||||
const oauth = await authService.getAvailableOAuth();
|
||||
setOAuth(oauth.data);
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
getAvailableOAuth().catch(toast.axiosError);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Container size={420} my={40}>
|
||||
<Title order={2} align="center" weight={900}>
|
||||
@@ -107,9 +129,7 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
||||
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
|
||||
<form
|
||||
onSubmit={form.onSubmit((values) => {
|
||||
if (showTotp)
|
||||
signInTotp(values.emailOrUsername, values.password, values.totp);
|
||||
else signIn(values.emailOrUsername, values.password);
|
||||
signIn(values.emailOrUsername, values.password);
|
||||
})}
|
||||
>
|
||||
<TextInput
|
||||
@@ -123,15 +143,6 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
||||
mt="md"
|
||||
{...form.getInputProps("password")}
|
||||
/>
|
||||
{showTotp && (
|
||||
<TextInput
|
||||
variant="filled"
|
||||
label={t("account.modal.totp.code")}
|
||||
placeholder="******"
|
||||
mt="md"
|
||||
{...form.getInputProps("totp")}
|
||||
/>
|
||||
)}
|
||||
{config.get("smtp.enabled") && (
|
||||
<Group position="right" mt="xs">
|
||||
<Anchor component={Link} href="/auth/resetPassword" size="xs">
|
||||
@@ -143,6 +154,27 @@ const SignInForm = ({ redirectPath }: { redirectPath: string }) => {
|
||||
<FormattedMessage id="signin.button.submit" />
|
||||
</Button>
|
||||
</form>
|
||||
{oauth.length > 0 && (
|
||||
<Stack mt="xl">
|
||||
<Group align="center" className={classes.or}>
|
||||
<Text>{t("signIn.oauth.or")}</Text>
|
||||
</Group>
|
||||
<Group position="center">
|
||||
{oauth.map((provider) => (
|
||||
<Button
|
||||
key={provider}
|
||||
component="a"
|
||||
target="_blank"
|
||||
title={t(`signIn.oauth.${provider}`)}
|
||||
href={getOAuthUrl(config.get("general.appUrl"), provider)}
|
||||
variant="light"
|
||||
>
|
||||
{getOAuthIcon(provider)}
|
||||
</Button>
|
||||
))}
|
||||
</Group>
|
||||
</Stack>
|
||||
)}
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
|
||||
84
frontend/src/components/auth/TotpForm.tsx
Normal file
84
frontend/src/components/auth/TotpForm.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Group,
|
||||
Paper,
|
||||
PinInput,
|
||||
Title,
|
||||
} from "@mantine/core";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useState } from "react";
|
||||
import authService from "../../services/auth.service";
|
||||
import toast from "../../utils/toast.util";
|
||||
import { useRouter } from "next/router";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
|
||||
function TotpForm({ redirectPath }: { redirectPath: string }) {
|
||||
const t = useTranslate();
|
||||
const router = useRouter();
|
||||
const { refreshUser } = useUser();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const validationSchema = yup.object().shape({
|
||||
code: yup
|
||||
.string()
|
||||
.min(6, t("common.error.too-short", { length: 6 }))
|
||||
.required(t("common.error.field-required")),
|
||||
});
|
||||
|
||||
const form = useForm({
|
||||
initialValues: {
|
||||
code: "",
|
||||
},
|
||||
validate: yupResolver(validationSchema),
|
||||
});
|
||||
|
||||
const onSubmit = async () => {
|
||||
if (loading) return;
|
||||
setLoading(true);
|
||||
try {
|
||||
await authService.signInTotp(
|
||||
form.values.code,
|
||||
router.query.loginToken as string,
|
||||
);
|
||||
await refreshUser();
|
||||
await router.replace(redirectPath);
|
||||
} catch (e) {
|
||||
toast.axiosError(e);
|
||||
form.setFieldError("code", "error");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Container size={420} my={40}>
|
||||
<Title order={2} align="center" weight={900}>
|
||||
<FormattedMessage id="totp.title" />
|
||||
</Title>
|
||||
<Paper withBorder shadow="md" p={30} mt={30} radius="md">
|
||||
<form onSubmit={form.onSubmit(onSubmit)}>
|
||||
<Group position="center">
|
||||
<PinInput
|
||||
length={6}
|
||||
oneTimeCode
|
||||
aria-label="One time code"
|
||||
autoFocus={true}
|
||||
onComplete={onSubmit}
|
||||
{...form.getInputProps("code")}
|
||||
/>
|
||||
<Button mt="md" type="submit" loading={loading}>
|
||||
{t("totp.button.signIn")}
|
||||
</Button>
|
||||
</Group>
|
||||
</form>
|
||||
</Paper>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default TotpForm;
|
||||
@@ -60,7 +60,7 @@ const Dropzone = ({
|
||||
toast.error(
|
||||
t("upload.dropzone.notify.file-too-big", {
|
||||
maxSize: byteToHumanSizeString(maxShareSize),
|
||||
})
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
files = files.map((newFile) => {
|
||||
|
||||
@@ -40,7 +40,7 @@ const showCreateUploadModal = (
|
||||
enableEmailRecepients: boolean;
|
||||
},
|
||||
files: FileUpload[],
|
||||
uploadCallback: (createShare: CreateShare, files: FileUpload[]) => void
|
||||
uploadCallback: (createShare: CreateShare, files: FileUpload[]) => void,
|
||||
) => {
|
||||
const t = translateOutsideContext();
|
||||
|
||||
@@ -137,7 +137,7 @@ const CreateUploadModalBody = ({
|
||||
maxViews: values.maxViews,
|
||||
},
|
||||
},
|
||||
files
|
||||
files,
|
||||
);
|
||||
modals.closeAll();
|
||||
}
|
||||
@@ -160,7 +160,7 @@ const CreateUploadModalBody = ({
|
||||
"link",
|
||||
Buffer.from(Math.random().toString(), "utf8")
|
||||
.toString("base64")
|
||||
.substr(10, 7)
|
||||
.substr(10, 7),
|
||||
)
|
||||
}
|
||||
>
|
||||
@@ -259,7 +259,7 @@ const CreateUploadModalBody = ({
|
||||
neverExpires: t("upload.modal.completed.never-expires"),
|
||||
expiresOn: t("upload.modal.completed.expires-on"),
|
||||
},
|
||||
form
|
||||
form,
|
||||
)}
|
||||
</Text>
|
||||
</>
|
||||
@@ -274,7 +274,7 @@ const CreateUploadModalBody = ({
|
||||
<Textarea
|
||||
variant="filled"
|
||||
placeholder={t(
|
||||
"upload.modal.accordion.description.placeholder"
|
||||
"upload.modal.accordion.description.placeholder",
|
||||
)}
|
||||
{...form.getInputProps("description")}
|
||||
/>
|
||||
@@ -298,7 +298,7 @@ const CreateUploadModalBody = ({
|
||||
if (!query.match(/^\S+@\S+\.\S+$/)) {
|
||||
form.setFieldError(
|
||||
"recipients",
|
||||
t("upload.modal.accordion.email.invalid-email")
|
||||
t("upload.modal.accordion.email.invalid-email"),
|
||||
);
|
||||
} else {
|
||||
form.setFieldError("recipients", null);
|
||||
@@ -324,7 +324,7 @@ const CreateUploadModalBody = ({
|
||||
<PasswordInput
|
||||
variant="filled"
|
||||
placeholder={t(
|
||||
"upload.modal.accordion.security.password.placeholder"
|
||||
"upload.modal.accordion.security.password.placeholder",
|
||||
)}
|
||||
label={t("upload.modal.accordion.security.password.label")}
|
||||
autoComplete="off"
|
||||
@@ -335,7 +335,7 @@ const CreateUploadModalBody = ({
|
||||
type="number"
|
||||
variant="filled"
|
||||
placeholder={t(
|
||||
"upload.modal.accordion.security.max-views.placeholder"
|
||||
"upload.modal.accordion.security.max-views.placeholder",
|
||||
)}
|
||||
label={t("upload.modal.accordion.security.max-views.label")}
|
||||
{...form.getInputProps("maxViews")}
|
||||
|
||||
@@ -4,12 +4,14 @@ import english from "./translations/en-US";
|
||||
import spanish from "./translations/es-ES";
|
||||
import finnish from "./translations/fi-FI";
|
||||
import french from "./translations/fr-FR";
|
||||
import japanese from "./translations/ja-JP";
|
||||
import dutch from "./translations/nl-BE";
|
||||
import portuguese from "./translations/pt-BR";
|
||||
import russian from "./translations/ru-RU";
|
||||
import serbian from "./translations/sr-SP";
|
||||
import thai from "./translations/th-TH";
|
||||
import chineseSimplified from "./translations/zh-CN";
|
||||
import polish from "./translations/pl-PL";
|
||||
|
||||
export const LOCALES = {
|
||||
ENGLISH: {
|
||||
@@ -72,4 +74,14 @@ export const LOCALES = {
|
||||
code: "nl-BE",
|
||||
messages: dutch,
|
||||
},
|
||||
JAPANESE: {
|
||||
name: "日本語",
|
||||
code: "ja-JP",
|
||||
messages: japanese,
|
||||
},
|
||||
POLISH: {
|
||||
name: "Polski",
|
||||
code: "pl-PL",
|
||||
messages: polish,
|
||||
},
|
||||
};
|
||||
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "Log ind",
|
||||
"signIn.notify.totp-required.title": "2-faktor login påkrævet",
|
||||
"signIn.notify.totp-required.description": "Indtast den aktuelle engangskode fra din 2-faktor Authenticator",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Opret en bruger",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "Din e-mail",
|
||||
"signup.button.submit": "Lad os komme i gang",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Glemt din adgangskode?",
|
||||
"resetPassword.description": "Indtast din e-mail for at nulstille din adgangskode.",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "Adgangskode",
|
||||
"account.card.password.old": "Gammel adgangskode",
|
||||
"account.card.password.new": "Ny adgangskode",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Adgangskoden er ændret",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Sikkerhed",
|
||||
"account.card.security.totp.enable.description": "Indtast din nuværende adgangskode for at begynde opsætningen af 2-faktor login",
|
||||
"account.card.security.totp.disable.description": "Indtast din nuværende adgangskode for at begynde opsætningen af 2-faktor login",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "Del",
|
||||
"admin.config.category.email": "E-mail",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "App-navn",
|
||||
"admin.config.general.app-name.description": "Navnet på applikationen",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "Adgangskode",
|
||||
"admin.config.smtp.password.description": "Adgangskoden til SMTP serveren",
|
||||
"admin.config.smtp.button.test": "Send test e-mail",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Ups! Denne side findes ikke.",
|
||||
"404.button.home": "Gå tilbage",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Gem",
|
||||
"common.button.create": "Opret",
|
||||
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "Anmelden",
|
||||
"signIn.notify.totp-required.title": "Zwei-Faktor-Authentifizierung benötigt",
|
||||
"signIn.notify.totp-required.description": "Bitte füge deinen Zwei-Faktor-Authentifizierungscode ein",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Erstelle ein Konto",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "Deine Emailadresse",
|
||||
"signup.button.submit": "Lass uns loslegen",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Passwort vergessen?",
|
||||
"resetPassword.description": "Gib deine Email Adresse ein, um dein Passwort zurückzusetzen.",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "Passwort",
|
||||
"account.card.password.old": "Altes Passwort",
|
||||
"account.card.password.new": "Neues Passwort",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Passwort erfolgreich geändert",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Sicherheit",
|
||||
"account.card.security.totp.enable.description": "Gib dein aktuelles Passwort ein, um TOTP zu aktivieren",
|
||||
"account.card.security.totp.disable.description": "Gib dein aktuelles Passwort ein, um TOTP zu deaktivieren",
|
||||
@@ -90,7 +113,7 @@ export default {
|
||||
// /account/shares
|
||||
"account.shares.title": "Meine Freigaben",
|
||||
"account.shares.title.empty": "Es ist so leer hier 👀",
|
||||
"account.shares.description.empty": "Du hast keine Freigaben eingerichtet.",
|
||||
"account.shares.description.empty": "Du hast keine Freigaben erstellt.",
|
||||
"account.shares.button.create": "Erstelle eine",
|
||||
"account.shares.info.title": "Teile deine Information",
|
||||
"account.shares.table.id": "ID",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "Freigabe",
|
||||
"admin.config.category.email": "E-Mail",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "App-Name",
|
||||
"admin.config.general.app-name.description": "Name der Applikation",
|
||||
"admin.config.general.app-url": "App-URL",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "Passwort",
|
||||
"admin.config.smtp.password.description": "Passwort des SMTP-Servers",
|
||||
"admin.config.smtp.button.test": "Test-E-Mail senden",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Ups, diese Seite existiert nicht.",
|
||||
"404.button.home": "Zurück zur Startseite",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Speichern",
|
||||
"common.button.create": "Erstellen",
|
||||
|
||||
@@ -43,6 +43,12 @@ export default {
|
||||
"signIn.notify.totp-required.title": "Two-factor authentication required",
|
||||
"signIn.notify.totp-required.description":
|
||||
"Please enter your two-factor authentication code",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
|
||||
// END /auth/signin
|
||||
|
||||
@@ -58,6 +64,12 @@ export default {
|
||||
|
||||
// END /auth/signup
|
||||
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
|
||||
// END /auth/totp
|
||||
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Forgot your password?",
|
||||
"resetPassword.description": "Enter your email to reset your password.",
|
||||
@@ -81,8 +93,23 @@ export default {
|
||||
"account.card.password.title": "Password",
|
||||
"account.card.password.old": "Old password",
|
||||
"account.card.password.new": "New password",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Password changed successfully",
|
||||
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
|
||||
|
||||
"account.card.security.title": "Security",
|
||||
"account.card.security.totp.enable.description":
|
||||
"Enter your current password to start enabling TOTP",
|
||||
@@ -336,6 +363,7 @@ export default {
|
||||
"admin.config.category.share": "Share",
|
||||
"admin.config.category.email": "Email",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
|
||||
"admin.config.general.app-name": "App name",
|
||||
"admin.config.general.app-name.description": "Name of the application",
|
||||
@@ -407,10 +435,66 @@ export default {
|
||||
"admin.config.smtp.password.description": "Password of the SMTP server",
|
||||
"admin.config.smtp.button.test": "Send test email",
|
||||
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
|
||||
// 404
|
||||
"404.description": "Oops this page doesn't exist.",
|
||||
"404.button.home": "Bring me back home",
|
||||
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
|
||||
// Common translations
|
||||
"common.button.save": "Save",
|
||||
"common.button.create": "Create",
|
||||
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "Iniciar sesión",
|
||||
"signIn.notify.totp-required.title": "Se requiere autenticación de dos factores",
|
||||
"signIn.notify.totp-required.description": "Por favor ingrese su código de autenticación de dos factores",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Crear una cuenta",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "Tu correo",
|
||||
"signup.button.submit": "Comencemos",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "¿Olvidaste tu contraseña?",
|
||||
"resetPassword.description": "Ingresa tu correo para restablecer tu contraseña.",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "Contraseña",
|
||||
"account.card.password.old": "Anterior contraseña",
|
||||
"account.card.password.new": "Nueva contraseña",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Contraseña cambiada correctamente",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Seguridad",
|
||||
"account.card.security.totp.enable.description": "Ingrese su contraseña actual para habilitar TOTP",
|
||||
"account.card.security.totp.disable.description": "Ingrese su contraseña actual para deshabilitar TOTP",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "Compartido",
|
||||
"admin.config.category.email": "Correo",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "Nombre de la App",
|
||||
"admin.config.general.app-name.description": "Nombre de la aplicación",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "Contraseña",
|
||||
"admin.config.smtp.password.description": "Contraseña del servidor SMTP",
|
||||
"admin.config.smtp.button.test": "Enviar correo de prueba",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Oops esta página no existe.",
|
||||
"404.button.home": "Regrésame al inicio",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Guardar",
|
||||
"common.button.create": "Crear",
|
||||
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "Kirjaudu sisään",
|
||||
"signIn.notify.totp-required.title": "Kaksivaiheinen tunnistautuminen vaadittu",
|
||||
"signIn.notify.totp-required.description": "Syötä kaksivaiheisen tunnistautumisen koodi tähän",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Rekisteröidy",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "Sähköpostisi",
|
||||
"signup.button.submit": "Aloitetaan",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Unohditko salasanan?",
|
||||
"resetPassword.description": "Kirjoita sähköpostiosoitteesi palauttaaksesi salasanasi.",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "Salasana",
|
||||
"account.card.password.old": "Vanha salasana",
|
||||
"account.card.password.new": "Uusi salasana",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Salasana vaihdettu",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Turvallisuus",
|
||||
"account.card.security.totp.enable.description": "Anna nykyinen salasanasi aloittaaksesi TOTP käytön",
|
||||
"account.card.security.totp.disable.description": "Syötä nykyinen salasanasi poistaaksesi TOTP käytöstä",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "Jako",
|
||||
"admin.config.category.email": "Sähköposti",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "Sovelluksen nimi",
|
||||
"admin.config.general.app-name.description": "Sovelluksen nimi",
|
||||
"admin.config.general.app-url": "Sovelluksen URL",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "Salasana",
|
||||
"admin.config.smtp.password.description": "SMTP palvelimen salasana",
|
||||
"admin.config.smtp.button.test": "Lähetä testisähköposti",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Hups tätä sivua ei ole olemassa.",
|
||||
"404.button.home": "Tuo minut takaisin kotiin",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Tallenna",
|
||||
"common.button.create": "Luo",
|
||||
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "Se connecter",
|
||||
"signIn.notify.totp-required.title": "Une authentification à deux facteurs est requise",
|
||||
"signIn.notify.totp-required.description": "Veuillez entrer votre code d'authentification à deux facteurs",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Créer un compte",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "Votre adresse email",
|
||||
"signup.button.submit": "Commençons",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Mot de passe oublié ?",
|
||||
"resetPassword.description": "Saisissez votre email pour réinitialiser votre mot de passe.",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "Mot de passe",
|
||||
"account.card.password.old": "Ancien mot de passe",
|
||||
"account.card.password.new": "Nouveau mot de passe",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Le mot de passe a été modifié avec succès",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Sécurité",
|
||||
"account.card.security.totp.enable.description": "Entrez votre mot de passe actuel pour commencer à activer TOTP",
|
||||
"account.card.security.totp.disable.description": "Entrez votre mot de passe pour désactiver TOTP",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "Partage",
|
||||
"admin.config.category.email": "Courriel",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "Nom de l'appli",
|
||||
"admin.config.general.app-name.description": "Le nom de l'application",
|
||||
"admin.config.general.app-url": "URL de l’appli",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "Mot de passe",
|
||||
"admin.config.smtp.password.description": "Mot de passe du serveur SMTP",
|
||||
"admin.config.smtp.button.test": "Envoyer un email de test",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Désolé, mais cette page n’existe pas.",
|
||||
"404.button.home": "Retour à l'accueil",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Sauvegarder",
|
||||
"common.button.create": "Créer",
|
||||
|
||||
402
frontend/src/i18n/translations/ja-JP.ts
Normal file
402
frontend/src/i18n/translations/ja-JP.ts
Normal file
@@ -0,0 +1,402 @@
|
||||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "アップロード",
|
||||
"navbar.signin": "サインイン",
|
||||
"navbar.home": "ホーム",
|
||||
"navbar.signup": "会員登録",
|
||||
"navbar.links.shares": "自分の共有",
|
||||
"navbar.links.reverse": "ファイルリクエスト",
|
||||
"navbar.avatar.account": "マイアカウント",
|
||||
"navbar.avatar.admin": "管理画面",
|
||||
"navbar.avatar.signout": "サインアウト",
|
||||
// END navbar
|
||||
// /
|
||||
"home.title": "<h>セルフホスト</h>のファイル共有プラットフォーム。",
|
||||
"home.description": "WeTransferのようなサードパーティーサービスに自分のファイルを渡したいですか?",
|
||||
"home.bullet.a.name": "セルフホスト",
|
||||
"home.bullet.a.description": "Pingvin Shareをあなたのマシンでホストしましょう。",
|
||||
"home.bullet.b.name": "プライバシー",
|
||||
"home.bullet.b.description": "あなたのファイルはあなたのものであり、決して第三者の手に入るべきではありません。",
|
||||
"home.bullet.c.name": "ファイルサイズ制限に悩まされることはありません",
|
||||
"home.bullet.c.description": "大きなファイルもアップロード。ストレージのサイズだけが唯一の制限です。",
|
||||
"home.button.start": "始めましょう",
|
||||
"home.button.source": "ソースコード",
|
||||
// END /
|
||||
// /auth/signin
|
||||
"signin.title": "おかえりなさい",
|
||||
"signin.description": "アカウントをお持ちではありませんか?",
|
||||
"signin.button.signup": "会員登録",
|
||||
"signin.input.email-or-username": "メールアドレスまたはユーザー名",
|
||||
"signin.input.email-or-username.placeholder": "メールアドレスまたはユーザー名",
|
||||
"signin.input.password": "パスワード",
|
||||
"signin.input.password.placeholder": "あなたのパスワード",
|
||||
"signin.button.submit": "サインイン",
|
||||
"signIn.notify.totp-required.title": "二段階認証が必要です",
|
||||
"signIn.notify.totp-required.description": "二段階認証コードを入力してください",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "アカウントを作成",
|
||||
"signup.description": "既にアカウントをお持ちですか?",
|
||||
"signup.button.signin": "サインイン",
|
||||
"signup.input.username": "ユーザー名",
|
||||
"signup.input.username.placeholder": "あなたのユーザー名",
|
||||
"signup.input.email": "メールアドレス",
|
||||
"signup.input.email.placeholder": "あなたのメールアドレス",
|
||||
"signup.button.submit": "さあ始めましょう",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "パスワードを忘れてしまいましたか?",
|
||||
"resetPassword.description": "登録しているメールアドレスを入力してください。",
|
||||
"resetPassword.notify.success": "パスワードをリセットするためのリンクをメールで送信しました。",
|
||||
"resetPassword.button.back": "サインインページに戻る",
|
||||
"resetPassword.text.resetPassword": "パスワードをリセット",
|
||||
"resetPassword.text.enterNewPassword": "新規パスワードを入力",
|
||||
"resetPassword.input.password": "新規パスワード",
|
||||
"resetPassword.notify.passwordReset": "パスワードのリセットに成功しました。",
|
||||
// /account
|
||||
"account.title": "マイアカウント",
|
||||
"account.card.info.title": "アカウント情報",
|
||||
"account.card.info.username": "ユーザー名",
|
||||
"account.card.info.email": "メールアドレス",
|
||||
"account.notify.info.success": "アカウントの更新に成功しました",
|
||||
"account.card.password.title": "パスワード",
|
||||
"account.card.password.old": "現在のパスワード",
|
||||
"account.card.password.new": "新規パスワード",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "パスワードの変更に成功しました",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "セキュリティ",
|
||||
"account.card.security.totp.enable.description": "2段階認証を有効にするため、現在のパスワードを入力してください",
|
||||
"account.card.security.totp.disable.description": "2段階認証を無効にするため、現在のパスワードを入力してください",
|
||||
"account.card.security.totp.button.start": "開始",
|
||||
"account.modal.totp.title": "2段階認証を有効にする",
|
||||
"account.modal.totp.step1": "ステップ1: 認証アプリを追加する",
|
||||
"account.modal.totp.step2": "ステップ2: コードを検証",
|
||||
"account.modal.totp.enterManually": "手動で入力",
|
||||
"account.modal.totp.code": "コピー",
|
||||
"account.modal.totp.clickToCopy": "ここをクリックしてコピー",
|
||||
"account.modal.totp.verify": "検証",
|
||||
"account.notify.totp.disable": "2段階認証の無効化に成功しました",
|
||||
"account.notify.totp.enable": "2段階認証の有効化に成功しました",
|
||||
"account.card.language.title": "言語",
|
||||
"account.card.language.description": "プロジェクトはコミュニティによって翻訳されています。一部の言語の翻訳は不完全の場合があります。",
|
||||
"account.card.color.title": "カラースキーム",
|
||||
// ThemeSwitcher.tsx
|
||||
"account.theme.dark": "ダーク",
|
||||
"account.theme.light": "ライト",
|
||||
"account.theme.system": "システムに合わせる",
|
||||
"account.button.delete": "アカウントを削除",
|
||||
"account.modal.delete.title": "アカウントを削除",
|
||||
"account.modal.delete.description": "全ての有効な共有を含め、アカウントに関する全てのデータを完全に削除してもよろしいですか?",
|
||||
// END /account
|
||||
// /account/shares
|
||||
"account.shares.title": "自分の共有",
|
||||
"account.shares.title.empty": "まだ何もありません 👀",
|
||||
"account.shares.description.empty": "共有しているアイテムがありません。",
|
||||
"account.shares.button.create": "新規作成",
|
||||
"account.shares.info.title": "共有情報",
|
||||
"account.shares.table.id": "ID",
|
||||
"account.shares.table.name": "名前",
|
||||
"account.shares.table.description": "説明",
|
||||
"account.shares.table.visitors": "訪問者",
|
||||
"account.shares.table.expiresAt": "有効期限:",
|
||||
"account.shares.table.createdAt": "作成日:",
|
||||
"account.shares.table.size": "サイズ",
|
||||
"account.shares.modal.share-informations": "共有情報",
|
||||
"account.shares.modal.share-link": "共有リンク",
|
||||
"account.shares.modal.delete.title": "共有を削除 {share}",
|
||||
"account.shares.modal.delete.description": "この共有を削除してもよろしいですか?",
|
||||
// END /account/shares
|
||||
// /account/reverseShares
|
||||
"account.reverseShares.title": "ファイルリクエスト",
|
||||
"account.reverseShares.description": "ファイルリクエストは、外部のユーザーにファイルをアップロードしてもらえるユニークなURLを生成できます。",
|
||||
"account.reverseShares.title.empty": "まだ何もありません 👀",
|
||||
"account.reverseShares.description.empty": "ファイルリクエストがありません。",
|
||||
// showCreateReverseShareModal.tsx
|
||||
"account.reverseShares.modal.title": "ファイルリクエストを作成",
|
||||
"account.reverseShares.modal.expiration.label": "有効期限",
|
||||
"account.reverseShares.modal.expiration.minute-singular": "分間",
|
||||
"account.reverseShares.modal.expiration.minute-plural": "分間",
|
||||
"account.reverseShares.modal.expiration.hour-singular": "時間",
|
||||
"account.reverseShares.modal.expiration.hour-plural": "時間",
|
||||
"account.reverseShares.modal.expiration.day-singular": "日間",
|
||||
"account.reverseShares.modal.expiration.day-plural": "日間",
|
||||
"account.reverseShares.modal.expiration.week-singular": "週間",
|
||||
"account.reverseShares.modal.expiration.week-plural": "週間",
|
||||
"account.reverseShares.modal.expiration.month-singular": "ヶ月",
|
||||
"account.reverseShares.modal.expiration.month-plural": "ヶ月",
|
||||
"account.reverseShares.modal.expiration.year-singular": "年間",
|
||||
"account.reverseShares.modal.expiration.year-plural": "年間",
|
||||
"account.reverseShares.modal.max-size.label": "最大ファイルサイズ",
|
||||
"account.reverseShares.modal.send-email": "メール通知を送信",
|
||||
"account.reverseShares.modal.send-email.description": "このファイルリクエストリンクを使用して、ファイルがアップロードされた場合にメールで通知します。",
|
||||
"account.reverseShares.modal.max-use.label": "最大回数",
|
||||
"account.reverseShares.modal.max-use.description": "このURLを使用してファイルをアップロードできる最大回数です。",
|
||||
"account.reverseShare.never-expires": "このファイルリクエストリンクは期限切れになりません。",
|
||||
"account.reverseShare.expires-on": "このファイルリクエストリンクは、{expiration} に期限切れとなります。",
|
||||
"account.reverseShares.table.no-shares": "まだファイルがアップロードされていません",
|
||||
"account.reverseShares.table.count.singular": "共有",
|
||||
"account.reverseShares.table.count.plural": "共有",
|
||||
"account.reverseShares.table.shares": "共有",
|
||||
"account.reverseShares.table.remaining": "残り使用回数",
|
||||
"account.reverseShares.table.max-size": "最大ファイルサイズ",
|
||||
"account.reverseShares.table.expires": "有効期限",
|
||||
"account.reverseShares.modal.reverse-share-link": "ファイルリクエストリンク",
|
||||
"account.reverseShares.modal.delete.title": "ファイルリクエストを削除",
|
||||
"account.reverseShares.modal.delete.description": "本当にこのファイルリクエストを削除しますか?削除すると、関連するファイルアップロードも削除されます。",
|
||||
// END /account/reverseShares
|
||||
// /admin
|
||||
"admin.title": "管理画面",
|
||||
"admin.button.users": "ユーザー管理",
|
||||
"admin.button.config": "設定",
|
||||
"admin.version": "バージョン",
|
||||
// END /admin
|
||||
// /admin/users
|
||||
"admin.users.title": "ユーザー管理",
|
||||
"admin.users.table.username": "ユーザー名",
|
||||
"admin.users.table.email": "メールアドレス",
|
||||
"admin.users.table.admin": "管理画面",
|
||||
"admin.users.edit.update.title": "ユーザー「{username}」を更新",
|
||||
"admin.users.edit.update.admin-privileges": "管理者権限",
|
||||
"admin.users.edit.update.change-password.title": "パスワードを変更",
|
||||
"admin.users.edit.update.change-password.field": "新規パスワード",
|
||||
"admin.users.edit.update.change-password.button": "新しいパスワードを保存",
|
||||
"admin.users.edit.update.notify.password.success": "パスワードの変更に成功しました",
|
||||
"admin.users.edit.delete.title": "ユーザーを削除 {username}",
|
||||
"admin.users.edit.delete.description": "このユーザーとこのユーザーに関連する全てのファイルアップロードを削除してもよろしいですか?",
|
||||
// showCreateUserModal.tsx
|
||||
"admin.users.modal.create.title": "ユーザーを作成",
|
||||
"admin.users.modal.create.username": "ユーザー名",
|
||||
"admin.users.modal.create.email": "メールアドレス",
|
||||
"admin.users.modal.create.password": "パスワード",
|
||||
"admin.users.modal.create.manual-password": "パスワードを手動で設定",
|
||||
"admin.users.modal.create.manual-password.description": "チェックされていない場合、ユーザーにパスワードを設定する為のリンクが記載されたメールを送信します。",
|
||||
"admin.users.modal.create.admin": "管理者権限",
|
||||
"admin.users.modal.create.admin.description": "チェックされている場合、ユーザーは管理画面にアクセスできるようになります。",
|
||||
// END /admin/users
|
||||
// /upload
|
||||
"upload.title": "アップロード",
|
||||
"upload.notify.generic-error": "共有を仕上げている最中にエラーが発生しました。",
|
||||
"upload.notify.count-failed": "{count} ファイルがアップロードに失敗しました。再度お試しください。",
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "ファイルをアップロード",
|
||||
"upload.dropzone.description": "ここにファイルをドラッグしてアップロードを開始しましょう。{maxSize} 以下のファイルがアップロードできます。",
|
||||
"upload.dropzone.notify.file-too-big": "アップロードしようとしたファイルは、最大ファイルサイズの{maxSize} を超えています。",
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "ファイル名",
|
||||
"upload.filelist.size": "サイズ",
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "共有を作成",
|
||||
"upload.modal.link.error.invalid": "文字、数字、アンダースコア、ハイフンのみ使用できます",
|
||||
"upload.modal.link.error.taken": "このリンクは既に使用されています",
|
||||
"upload.modal.not-signed-in": "サインインしていません",
|
||||
"upload.modal.not-signed-in-description": "共有の手動削除と訪問者カウンターは表示できません。",
|
||||
"upload.modal.expires.never": "永久",
|
||||
"upload.modal.expires.never-long": "期限切れにさせない",
|
||||
"upload.modal.link.label": "リンク",
|
||||
"upload.modal.expires.label": "有効期限",
|
||||
"upload.modal.expires.minute-singular": "分間",
|
||||
"upload.modal.expires.minute-plural": "分間",
|
||||
"upload.modal.expires.hour-singular": "時間",
|
||||
"upload.modal.expires.hour-plural": "時間",
|
||||
"upload.modal.expires.day-singular": "日間",
|
||||
"upload.modal.expires.day-plural": "日間",
|
||||
"upload.modal.expires.week-singular": "週間",
|
||||
"upload.modal.expires.week-plural": "週間",
|
||||
"upload.modal.expires.month-singular": "ヶ月間",
|
||||
"upload.modal.expires.month-plural": "ヶ月間",
|
||||
"upload.modal.expires.year-singular": "年間",
|
||||
"upload.modal.expires.year-plural": "年間",
|
||||
"upload.modal.accordion.description.title": "説明",
|
||||
"upload.modal.accordion.description.placeholder": "この共有に関する受信者へのメモ",
|
||||
"upload.modal.accordion.email.title": "メールで受け取る相手",
|
||||
"upload.modal.accordion.email.placeholder": "メールの宛先を入力",
|
||||
"upload.modal.accordion.email.invalid-email": "無効なメールアドレスです",
|
||||
"upload.modal.accordion.security.title": "セキュリティオプション",
|
||||
"upload.modal.accordion.security.password.label": "パスワード保護",
|
||||
"upload.modal.accordion.security.password.placeholder": "パスワードなし",
|
||||
"upload.modal.accordion.security.max-views.label": "最大閲覧回数",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "制限なし",
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "この共有は期限切れになりません。",
|
||||
"upload.modal.completed.expires-on": "この共有は、{expiration} に期限切れとなります。",
|
||||
"upload.modal.completed.share-ready": "共有の準備ができました",
|
||||
// END /upload
|
||||
// /share/[id]
|
||||
"share.title": "「{shareId}」が共有されました",
|
||||
"share.description": "あなたと共有したファイルをご確認ください!",
|
||||
"share.error.visitor-limit-exceeded.title": "訪問者の上限を超えました",
|
||||
"share.error.visitor-limit-exceeded.description": "この共有からの訪問者の回数が制限を超えています。",
|
||||
"share.error.removed.title": "共有が削除されました",
|
||||
"share.error.not-found.title": "共有が見つかりません",
|
||||
"share.error.not-found.description": "お探しの共有が見つかりません。",
|
||||
"share.modal.password.title": "パスワードが必要です",
|
||||
"share.modal.password.description": "この共有にアクセスするには、共有相手から伝えられているパスワードを入力してください。",
|
||||
"share.modal.password": "パスワード",
|
||||
"share.modal.error.invalid-password": "パスワードが間違っています",
|
||||
"share.button.download-all": "全てダウンロード",
|
||||
"share.notify.download-all-preparing": "共有は準備中です。数分後に再度お試しください。",
|
||||
"share.modal.file-link": "ファイルリンク",
|
||||
"share.table.name": "ファイル名",
|
||||
"share.table.size": "サイズ",
|
||||
"share.modal.file-preview.error.not-supported.title": "プレビューに対応していません",
|
||||
"share.modal.file-preview.error.not-supported.description": "これらのファイルのプレビューには対応していません。ファイルをダウンロードして、直接確認してください。",
|
||||
// END /share/[id]
|
||||
// /admin/config
|
||||
"admin.config.title": "設定",
|
||||
"admin.config.category.general": "一般",
|
||||
"admin.config.category.share": "共有",
|
||||
"admin.config.category.email": "メール",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "アプリ名",
|
||||
"admin.config.general.app-name.description": "アプリの名前",
|
||||
"admin.config.general.app-url": "アプリ名",
|
||||
"admin.config.general.app-url.description": "Pingvin Shareで利用できるURL",
|
||||
"admin.config.general.show-home-page": "ホームページを表示する",
|
||||
"admin.config.general.show-home-page.description": "ホームページを表示するかどうか選択",
|
||||
"admin.config.general.logo": "ロゴ",
|
||||
"admin.config.general.logo.description": "新しい画像をアップロードしてロゴを変更できます。画像は、PNG形式でアスペクト比が1:1である必要があります。",
|
||||
"admin.config.general.logo.placeholder": "画像を選択",
|
||||
"admin.config.email.enable-share-email-recipients": "メールでの共有を有効にする",
|
||||
"admin.config.email.enable-share-email-recipients.description": "メールで共有を送信できるようにするか選択してください。SMTPが有効になっている場合にのみ機能します。",
|
||||
"admin.config.email.share-recipients-subject": "宛先への件名",
|
||||
"admin.config.email.share-recipients-subject.description": "メールで共有された相手に送信メールの件名です。",
|
||||
"admin.config.email.share-recipients-message": "宛先への本文",
|
||||
"admin.config.email.share-recipients-message.description": "メールで共有された相手に送信されるメールの本文です。次の変数が利用できます:\n{creator} - 共有作成者のユーザー名\n{shareUrl} - 共有のURL\n{desc} - 共有の説明\n{expires} - 共有の有効期限\n変数は、実際の値によって置き換えられます。",
|
||||
"admin.config.email.reverse-share-subject": "ファイルリクエストの件名",
|
||||
"admin.config.email.reverse-share-subject.description": "あなたが作成したファイルリクエストリンクからファイルがアップロードされた場合に送信されるメールの件名です。",
|
||||
"admin.config.email.reverse-share-message": "ファイルリクエストの本文",
|
||||
"admin.config.email.reverse-share-message.description": "あなたが作成したファイルリクエストリンクからファイルがアップロードされた場合に送信されるメールの本文です。{shareUrl} は、作成者の名前とURLにより置き換えられます。",
|
||||
"admin.config.email.reset-password-subject": "パスワードリセットの件名",
|
||||
"admin.config.email.reset-password-subject.description": "パスワードリセットのリクエストがされた時に送信されるメールの件名です。",
|
||||
"admin.config.email.reset-password-message": "パスワードリセットの本文",
|
||||
"admin.config.email.reset-password-message.description": "あなたが作成したファイルリクエストリンクからファイルがアップロードされた場合に送信されるメールの本文です。{url} は、実際のパスワードリセットURLによって置き換えられます。",
|
||||
"admin.config.email.invite-subject": "無効な件名",
|
||||
"admin.config.email.invite-subject.description": "管理者がユーザーを招待したときに送信されるメールの件名です。",
|
||||
"admin.config.email.invite-message": "無効な本文",
|
||||
"admin.config.email.invite-message.description": "管理者がユーザーを招待したときに送信されるメールの本文です。{url} は、招待URLに、{password} は、パスワードに置き換えられます。",
|
||||
"admin.config.share.allow-registration": "登録を許可する",
|
||||
"admin.config.share.allow-registration.description": "登録を許可するかどうかを選択してください。",
|
||||
"admin.config.share.allow-unauthenticated-shares": "ログインしていない状態での共有を許可する",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "ログインしていないユーザーに共有の作成を許可するかどうかを選択してください。",
|
||||
"admin.config.share.max-size": "最大ファイルサイズ",
|
||||
"admin.config.share.max-size.description": "最大ファイルサイズ(byte単位)",
|
||||
"admin.config.share.zip-compression-level": "Zip圧縮レベル",
|
||||
"admin.config.share.zip-compression-level.description": "ファイルサイズと圧縮速度のバランスを取るように、レベルを調整できます。有効な値は0~9の間で、0が無圧縮、9で最大限の圧縮となります。 ",
|
||||
"admin.config.smtp.enabled": "有効",
|
||||
"admin.config.smtp.enabled.description": "SMTPを有効にするかどうかを選択してください。SMTPサーバーのホスト名、ポート番号、電子メールアドレス、ユーザー名、パスワードが入力されている場合にのみ、有効にしてください。",
|
||||
"admin.config.smtp.host": "ホスト名",
|
||||
"admin.config.smtp.host.description": "SMTPサーバーのホスト名",
|
||||
"admin.config.smtp.port": "ポート番号",
|
||||
"admin.config.smtp.port.description": "SMTPサーバーのポート番号",
|
||||
"admin.config.smtp.email": "メールアドレス",
|
||||
"admin.config.smtp.email.description": "送信メールに設定するメールアドレス",
|
||||
"admin.config.smtp.username": "ユーザー名",
|
||||
"admin.config.smtp.username.description": "SMTPサーバーのユーザー名",
|
||||
"admin.config.smtp.password": "パスワード",
|
||||
"admin.config.smtp.password.description": "SMTPサーバーのパスワード",
|
||||
"admin.config.smtp.button.test": "テストメールを送信",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "ページが見つかりません。",
|
||||
"404.button.home": "ホームに戻る",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "保存",
|
||||
"common.button.create": "作成",
|
||||
"common.button.submit": "送信",
|
||||
"common.button.delete": "削除",
|
||||
"common.button.cancel": "キャンセル",
|
||||
"common.button.confirm": "確認",
|
||||
"common.button.disable": "無効",
|
||||
"common.button.share": "共有",
|
||||
"common.button.generate": "生成",
|
||||
"common.button.done": "完了",
|
||||
"common.text.link": "リンク",
|
||||
"common.text.or": "または",
|
||||
"common.button.go-back": "戻る",
|
||||
"common.notify.copied": "リンクをクリップボードにコピーしました",
|
||||
"common.success": "成功",
|
||||
"common.error": "エラー",
|
||||
"common.error.unknown": "不明なエラーが発生しました",
|
||||
"common.error.invalid-email": "無効なメールアドレス",
|
||||
"common.error.too-short": "最低{length} 文字である必要があります",
|
||||
"common.error.too-long": "最大{length} 文字である必要があります",
|
||||
"common.error.exact-length": "{length} 文字である必要があります",
|
||||
"common.error.invalid-number": "数字でなければなりません",
|
||||
"common.error.field-required": "これは必須項目です"
|
||||
};
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "Aanmelden",
|
||||
"signIn.notify.totp-required.title": "Tweestapsverificatie vereist",
|
||||
"signIn.notify.totp-required.description": "Voer uw tweestapsverificatiecode in",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Account aanmaken",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "Uw e-mailadres",
|
||||
"signup.button.submit": "Laten we beginnen",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Wachtwoord vergeten?",
|
||||
"resetPassword.description": "Voer uw e-mailadres in om uw wachtwoord opnieuw in te stellen.",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "Wachtwoord",
|
||||
"account.card.password.old": "Oud wachtwoord",
|
||||
"account.card.password.new": "Nieuw wachtwoord",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Wachtwoord succesvol gewijzigd",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Beveiliging",
|
||||
"account.card.security.totp.enable.description": "Voer uw huidige wachtwoord in om TOTP in te schakelen",
|
||||
"account.card.security.totp.disable.description": "Voer uw huidige wachtwoord in om TOTP uit te schakelen",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "Delen",
|
||||
"admin.config.category.email": "E-mail",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "App naam",
|
||||
"admin.config.general.app-name.description": "Naam van de applicatie",
|
||||
"admin.config.general.app-url": "App URL",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "Wachtwoord",
|
||||
"admin.config.smtp.password.description": "Wachtwoord van de SMTP-server",
|
||||
"admin.config.smtp.button.test": "Test e-mail verzenden",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Oeps, deze pagina bestaat niet.",
|
||||
"404.button.home": "Breng me terug naar huis",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Opslaan",
|
||||
"common.button.create": "Aanmaken",
|
||||
|
||||
402
frontend/src/i18n/translations/pl-PL.ts
Normal file
402
frontend/src/i18n/translations/pl-PL.ts
Normal file
@@ -0,0 +1,402 @@
|
||||
export default {
|
||||
// Navbar
|
||||
"navbar.upload": "Prześlij",
|
||||
"navbar.signin": "Zaloguj się",
|
||||
"navbar.home": "Strona główna",
|
||||
"navbar.signup": "Zarejestruj się",
|
||||
"navbar.links.shares": "Moje udziały",
|
||||
"navbar.links.reverse": "Udziały odwrotne",
|
||||
"navbar.avatar.account": "Moje konto",
|
||||
"navbar.avatar.admin": "Administracja",
|
||||
"navbar.avatar.signout": "Wyloguj się",
|
||||
// END navbar
|
||||
// /
|
||||
"home.title": "<h>Samodzielnie utrzymywana</h> platforma udostępniania plików.",
|
||||
"home.description": "Czy na pewno chcesz przekazać swoje prywatne pliki w ręce osób trzecich, takich jak WeTransfer?",
|
||||
"home.bullet.a.name": "Samodzielnie Utrzymywana",
|
||||
"home.bullet.a.description": "Utrzymuj Pingvin Share na własnym serwerze.",
|
||||
"home.bullet.b.name": "Prywatność",
|
||||
"home.bullet.b.description": "To są twoje pliki i nigdy nie powinny trafiać w ręce osób trzecich.",
|
||||
"home.bullet.c.name": "Brak irytującego ograniczenia rozmiaru pliku",
|
||||
"home.bullet.c.description": "Prześlij tak duże pliki, jak chcesz. Ograniczeniem będzie tylko twój dysk twardy.",
|
||||
"home.button.start": "Zaczynajmy",
|
||||
"home.button.source": "Kod źródłowy",
|
||||
// END /
|
||||
// /auth/signin
|
||||
"signin.title": "Witaj ponownie",
|
||||
"signin.description": "Nie masz jeszcze konta?",
|
||||
"signin.button.signup": "Zarejestruj się",
|
||||
"signin.input.email-or-username": "Adres e-mail lub nazwa użytkownika",
|
||||
"signin.input.email-or-username.placeholder": "Adres e-mail lub nazwa użytkownika",
|
||||
"signin.input.password": "Hasło",
|
||||
"signin.input.password.placeholder": "Twoje hasło",
|
||||
"signin.button.submit": "Zaloguj się",
|
||||
"signIn.notify.totp-required.title": "Wymagane jest uwierzytelnianie dwuetapowe",
|
||||
"signIn.notify.totp-required.description": "Podaj kod logowania dwuetapowego",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Utwórz konto",
|
||||
"signup.description": "Masz już konto?",
|
||||
"signup.button.signin": "Zaloguj się",
|
||||
"signup.input.username": "Nazwa użytkownika",
|
||||
"signup.input.username.placeholder": "Twoja nazwa użytkownika",
|
||||
"signup.input.email": "Adres e-mail",
|
||||
"signup.input.email.placeholder": "Twój adres e-mail",
|
||||
"signup.button.submit": "Zaczynajmy",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Nie pamiętasz hasła?",
|
||||
"resetPassword.description": "Wprowadź swój e-mail, aby zresetować swoje hasło.",
|
||||
"resetPassword.notify.success": "Został wysłany e-mail z linkiem do resetowania hasła.",
|
||||
"resetPassword.button.back": "Powrót do strony logowania",
|
||||
"resetPassword.text.resetPassword": "Resetuj hasło",
|
||||
"resetPassword.text.enterNewPassword": "Wprowadź nowe hasło",
|
||||
"resetPassword.input.password": "Nowe hasło",
|
||||
"resetPassword.notify.passwordReset": "Hasło zostało pomyślnie zresetowane.",
|
||||
// /account
|
||||
"account.title": "Moje konto",
|
||||
"account.card.info.title": "Informacje o koncie",
|
||||
"account.card.info.username": "Nazwa użytkownika",
|
||||
"account.card.info.email": "Adres e-mail",
|
||||
"account.notify.info.success": "Konto zostało zaktualizowane",
|
||||
"account.card.password.title": "Hasło",
|
||||
"account.card.password.old": "Dotychczasowe hasło",
|
||||
"account.card.password.new": "Nowe hasło",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Hasło zostało pomyślnie zmienione",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Zabezpieczenia",
|
||||
"account.card.security.totp.enable.description": "Wprowadź aktualne hasło, aby móc włączyć TOTP",
|
||||
"account.card.security.totp.disable.description": "Wprowadź aktualne hasło, aby wyłączyć TOTP",
|
||||
"account.card.security.totp.button.start": "Uruchom",
|
||||
"account.modal.totp.title": "Uruchom TOTP",
|
||||
"account.modal.totp.step1": "Krok 1: Dodaj swój uwierzytelniacz",
|
||||
"account.modal.totp.step2": "Krok 2: Potwierdź swój kod",
|
||||
"account.modal.totp.enterManually": "Wpisz ręcznie",
|
||||
"account.modal.totp.code": "Kod",
|
||||
"account.modal.totp.clickToCopy": "Kliknij, aby skopiować",
|
||||
"account.modal.totp.verify": "Weryfikuj",
|
||||
"account.notify.totp.disable": "%s wyłączono pomyślnie",
|
||||
"account.notify.totp.enable": "TOTP włączono pomyślnie",
|
||||
"account.card.language.title": "Język",
|
||||
"account.card.language.description": "Projekt jest tłumaczony przez społeczność. Niektóre języki mogą być niekompletne.",
|
||||
"account.card.color.title": "Schemat kolorów",
|
||||
// ThemeSwitcher.tsx
|
||||
"account.theme.dark": "Ciemny",
|
||||
"account.theme.light": "Jasny",
|
||||
"account.theme.system": "System",
|
||||
"account.button.delete": "Usuń Konto",
|
||||
"account.modal.delete.title": "Usuń Konto",
|
||||
"account.modal.delete.description": "Czy na pewno chcesz usunąć swoje konto, w tym wszystkie aktywne udziały?",
|
||||
// END /account
|
||||
// /account/shares
|
||||
"account.shares.title": "Moje udziały",
|
||||
"account.shares.title.empty": "Tu jest pusto :eys:",
|
||||
"account.shares.description.empty": "Nie masz żadnych udziałów.",
|
||||
"account.shares.button.create": "Utwórz go",
|
||||
"account.shares.info.title": "Informacje udziału",
|
||||
"account.shares.table.id": "Nr identyfikacyjny",
|
||||
"account.shares.table.name": "Nazwa",
|
||||
"account.shares.table.description": "Opis",
|
||||
"account.shares.table.visitors": "Odwiedzający",
|
||||
"account.shares.table.expiresAt": "Wygasa",
|
||||
"account.shares.table.createdAt": "Utworzono",
|
||||
"account.shares.table.size": "Rozmiar",
|
||||
"account.shares.modal.share-informations": "Informacje udziału",
|
||||
"account.shares.modal.share-link": "Udostępnij link",
|
||||
"account.shares.modal.delete.title": "Usuń udostępnianie {share}",
|
||||
"account.shares.modal.delete.description": "Czy na pewno chcesz usunąć to udostępnianie?",
|
||||
// END /account/shares
|
||||
// /account/reverseShares
|
||||
"account.reverseShares.title": "Udziały odwrotne",
|
||||
"account.reverseShares.description": "Udostępnianie odwrotne pozwala na wygenerowanie unikalnego adresu URL, który umożliwia użytkownikom zewnętrznym na tworzenie udostępniania.",
|
||||
"account.reverseShares.title.empty": "Tu jest pusto :eys:",
|
||||
"account.reverseShares.description.empty": "Nie masz żadnych udostępnień odwrotnych.",
|
||||
// showCreateReverseShareModal.tsx
|
||||
"account.reverseShares.modal.title": "Utwórz udostępnianie odwrotne",
|
||||
"account.reverseShares.modal.expiration.label": "Wygasanie",
|
||||
"account.reverseShares.modal.expiration.minute-singular": "Minuta",
|
||||
"account.reverseShares.modal.expiration.minute-plural": "Minuty/t",
|
||||
"account.reverseShares.modal.expiration.hour-singular": "Godzina",
|
||||
"account.reverseShares.modal.expiration.hour-plural": "Godziny",
|
||||
"account.reverseShares.modal.expiration.day-singular": "Dzień",
|
||||
"account.reverseShares.modal.expiration.day-plural": "Dni",
|
||||
"account.reverseShares.modal.expiration.week-singular": "Tydzień",
|
||||
"account.reverseShares.modal.expiration.week-plural": "Tygodni/e",
|
||||
"account.reverseShares.modal.expiration.month-singular": "Miesiąc",
|
||||
"account.reverseShares.modal.expiration.month-plural": "Miesiące/ęcy",
|
||||
"account.reverseShares.modal.expiration.year-singular": "Rok",
|
||||
"account.reverseShares.modal.expiration.year-plural": "Lata/lat",
|
||||
"account.reverseShares.modal.max-size.label": "Maksymalny rozmiar udziału",
|
||||
"account.reverseShares.modal.send-email": "Wysyłanie powiadomienia e-mail",
|
||||
"account.reverseShares.modal.send-email.description": "Wyślij powiadomienie e-mail, gdy udostępnianie zostanie utworzone za pomocą linku udostępniania odwrotnego.",
|
||||
"account.reverseShares.modal.max-use.label": "Limit użyć",
|
||||
"account.reverseShares.modal.max-use.description": "Maksymalna ilość razy, kiedy ten adres URL może być użyty do utworzenia udostępniania.",
|
||||
"account.reverseShare.never-expires": "To udostępnienie odwrotne nigdy nie wygasa.",
|
||||
"account.reverseShare.expires-on": "To udostępnienie odwrotne wygaśnie dnia {expiration}.",
|
||||
"account.reverseShares.table.no-shares": "Nie utworzono jeszcze udostępnień",
|
||||
"account.reverseShares.table.count.singular": "udostępnij",
|
||||
"account.reverseShares.table.count.plural": "udziały",
|
||||
"account.reverseShares.table.shares": "Udziały",
|
||||
"account.reverseShares.table.remaining": "Pozostało miejsca",
|
||||
"account.reverseShares.table.max-size": "Maksymalny rozmiar udziału",
|
||||
"account.reverseShares.table.expires": "Wygasa",
|
||||
"account.reverseShares.modal.reverse-share-link": "Link udostępnienia odwrotnego",
|
||||
"account.reverseShares.modal.delete.title": "Usuń udostępnienie odwrotnie",
|
||||
"account.reverseShares.modal.delete.description": "Czy na pewno chcesz usunąć to udostępnienie odwrotne? Jeśli tak, to również zostaną usunięte powiązane z nim udostępnienia.",
|
||||
// END /account/reverseShares
|
||||
// /admin
|
||||
"admin.title": "Administracja",
|
||||
"admin.button.users": "Zarządzanie użytkownikami",
|
||||
"admin.button.config": "Konfiguracja",
|
||||
"admin.version": "Wersja",
|
||||
// END /admin
|
||||
// /admin/users
|
||||
"admin.users.title": "Zarządzanie użytkownikami",
|
||||
"admin.users.table.username": "Nazwa użytkownika",
|
||||
"admin.users.table.email": "Adres e-mail",
|
||||
"admin.users.table.admin": "Administrator",
|
||||
"admin.users.edit.update.title": "Zaktualizuj użytkownika {username}",
|
||||
"admin.users.edit.update.admin-privileges": "Uprawnienia administratora",
|
||||
"admin.users.edit.update.change-password.title": "Zmień hasło",
|
||||
"admin.users.edit.update.change-password.field": "Nowe hasło",
|
||||
"admin.users.edit.update.change-password.button": "Zapisz nowe hasło",
|
||||
"admin.users.edit.update.notify.password.success": "Hasło pomyślnie zmienione",
|
||||
"admin.users.edit.delete.title": "Usuń użytkownika {username}",
|
||||
"admin.users.edit.delete.description": "Czy na pewno chcesz usunąć tego użytkownika i wszystkie jego udziały?",
|
||||
// showCreateUserModal.tsx
|
||||
"admin.users.modal.create.title": "Utwórz użytkownika",
|
||||
"admin.users.modal.create.username": "Nazwa użytkownika",
|
||||
"admin.users.modal.create.email": "Adres e-mail",
|
||||
"admin.users.modal.create.password": "Hasło",
|
||||
"admin.users.modal.create.manual-password": "Ustaw ręcznie hasło",
|
||||
"admin.users.modal.create.manual-password.description": "Jeśli nie zaznaczone, użytkownik otrzyma e-mail z linkiem do ustawienia hasła.",
|
||||
"admin.users.modal.create.admin": "Uprawnienia administratora",
|
||||
"admin.users.modal.create.admin.description": "Jeśli zaznaczone, użytkownik będzie miał dostęp do panelu administratora.",
|
||||
// END /admin/users
|
||||
// /upload
|
||||
"upload.title": "Prześlij",
|
||||
"upload.notify.generic-error": "Wystąpił błąd podczas zakończenia tworzenia udziału.",
|
||||
"upload.notify.count-failed": "Nie udało się przesłać {count} plików. Próbuję ponownie.",
|
||||
// Dropzone.tsx
|
||||
"upload.dropzone.title": "Prześlij pliki",
|
||||
"upload.dropzone.description": "Przeciągnij i upuść pliki tutaj, aby rozpocząć udostępnianie. Możemy zaakceptować tylko pliki mniejsze niż {maxSize}.",
|
||||
"upload.dropzone.notify.file-too-big": "Twoje pliki przekraczają maksymalny rozmiar {maxSize}.",
|
||||
// FileList.tsx
|
||||
"upload.filelist.name": "Nazwa",
|
||||
"upload.filelist.size": "Rozmiar",
|
||||
// showCreateUploadModal.tsx
|
||||
"upload.modal.title": "Utwórz Udział",
|
||||
"upload.modal.link.error.invalid": "Może zawierać tylko litery, cyfry, znaki podkreślenia i myślniki",
|
||||
"upload.modal.link.error.taken": "Ten link jest już w użyciu",
|
||||
"upload.modal.not-signed-in": "Nie jesteś zalogowany/a",
|
||||
"upload.modal.not-signed-in-description": "Nie możesz ręcznie usunąć swojego udziału ani wyświetlić licznika odwiedzających.",
|
||||
"upload.modal.expires.never": "nigdy",
|
||||
"upload.modal.expires.never-long": "Nigdy nie wygasa",
|
||||
"upload.modal.link.label": "Link",
|
||||
"upload.modal.expires.label": "Wygasanie",
|
||||
"upload.modal.expires.minute-singular": "Minuta",
|
||||
"upload.modal.expires.minute-plural": "Minuty/t",
|
||||
"upload.modal.expires.hour-singular": "Godzina",
|
||||
"upload.modal.expires.hour-plural": "Godzin/y",
|
||||
"upload.modal.expires.day-singular": "Dzień",
|
||||
"upload.modal.expires.day-plural": "Dni",
|
||||
"upload.modal.expires.week-singular": "Tydzień",
|
||||
"upload.modal.expires.week-plural": "Tygodni/e",
|
||||
"upload.modal.expires.month-singular": "Miesiąc",
|
||||
"upload.modal.expires.month-plural": "Miesiące/ęcy",
|
||||
"upload.modal.expires.year-singular": "Rok",
|
||||
"upload.modal.expires.year-plural": "Lat/a",
|
||||
"upload.modal.accordion.description.title": "Opis",
|
||||
"upload.modal.accordion.description.placeholder": "Notatka dla odbiorców tego udziału",
|
||||
"upload.modal.accordion.email.title": "Odbiorcy wiadomości e-mail",
|
||||
"upload.modal.accordion.email.placeholder": "Wprowadź adresatów wiadomości e-mail",
|
||||
"upload.modal.accordion.email.invalid-email": "Adres e-mail jest nieprawidłowy",
|
||||
"upload.modal.accordion.security.title": "Ustawienia bezpieczeństwa",
|
||||
"upload.modal.accordion.security.password.label": "Ochrona hasłem",
|
||||
"upload.modal.accordion.security.password.placeholder": "Brak hasła",
|
||||
"upload.modal.accordion.security.max-views.label": "Maksymalna liczba wyświetleń",
|
||||
"upload.modal.accordion.security.max-views.placeholder": "Bez ograniczeń",
|
||||
// showCompletedUploadModal.tsx
|
||||
"upload.modal.completed.never-expires": "To udostępnienie nigdy nie wygaśnie.",
|
||||
"upload.modal.completed.expires-on": "To udostępnienie wygaśnie dnia {expiration}.",
|
||||
"upload.modal.completed.share-ready": "Udostępnianie gotowe",
|
||||
// END /upload
|
||||
// /share/[id]
|
||||
"share.title": "Udostępnij {shareId}",
|
||||
"share.description": "Spójrz, co ci udostępniłem!",
|
||||
"share.error.visitor-limit-exceeded.title": "Przekroczono limit odwiedzających",
|
||||
"share.error.visitor-limit-exceeded.description": "Limit odwiedzających dla tego udziału został przekroczony.",
|
||||
"share.error.removed.title": "Udostępnianie usunięte",
|
||||
"share.error.not-found.title": "Nie znaleziono udziału",
|
||||
"share.error.not-found.description": "Udział, który szukasz, nie istnieje.",
|
||||
"share.modal.password.title": "Wymagane hasło",
|
||||
"share.modal.password.description": "Aby uzyskać dostęp do tego udziału, wprowadź hasło.",
|
||||
"share.modal.password": "Hasło",
|
||||
"share.modal.error.invalid-password": "Hasło jest nieprawidłowe",
|
||||
"share.button.download-all": "Pobierz wszystko",
|
||||
"share.notify.download-all-preparing": "Udostępnianie jest przygotowywane. Spróbuj ponownie za kilka minut.",
|
||||
"share.modal.file-link": "Link do pliku",
|
||||
"share.table.name": "Nazwa",
|
||||
"share.table.size": "Rozmiar",
|
||||
"share.modal.file-preview.error.not-supported.title": "Podgląd nie jest obsługiwany",
|
||||
"share.modal.file-preview.error.not-supported.description": "Podgląd dla tego typu pliku nie jest obsługiwany. Pobierz plik, aby go zobaczyć.",
|
||||
// END /share/[id]
|
||||
// /admin/config
|
||||
"admin.config.title": "Konfiguracja",
|
||||
"admin.config.category.general": "Ogólne",
|
||||
"admin.config.category.share": "Udostępnij",
|
||||
"admin.config.category.email": "Adres e-mail",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "Nazwa aplikacji",
|
||||
"admin.config.general.app-name.description": "Nazwa aplikacji",
|
||||
"admin.config.general.app-url": "Adres URL aplikacji",
|
||||
"admin.config.general.app-url.description": "Pod którym adresem URL Pingvin Share jest dostępny",
|
||||
"admin.config.general.show-home-page": "Pokaż stronę główną",
|
||||
"admin.config.general.show-home-page.description": "Czy wyświetlać stronę główną",
|
||||
"admin.config.general.logo": "Logo",
|
||||
"admin.config.general.logo.description": "Zmień logo, przesyłając nowy obraz. Obraz musi być plikiem PNG i mieć proporcje 1:1.",
|
||||
"admin.config.general.logo.placeholder": "Wybierz obraz",
|
||||
"admin.config.email.enable-share-email-recipients": "Włącz adresatów wiadomości e-mail dla udziału",
|
||||
"admin.config.email.enable-share-email-recipients.description": "Czy zezwolić na wysyłanie e-maili do adresatów udziału? Włącz to tylko wtedy, gdy włączyłeś SMTP.",
|
||||
"admin.config.email.share-recipients-subject": "Temat dla odbiorców udziału",
|
||||
"admin.config.email.share-recipients-subject.description": "Temat wiadomości e-mail, która zostanie wysłana do odbiorców udziału.",
|
||||
"admin.config.email.share-recipients-message": "Wiadomość dla odbiorców udziału",
|
||||
"admin.config.email.share-recipients-message.description": "Wiadomość, która zostanie wysłana do odbiorców udziału. Dostępne zmienne:\n {creator} - Nazwa użytkownika twórcy udziału\n {shareUrl} - Adres URL udziału\n {desc} - Opis udziału\n {expires} - Data wygaśnięcia udziału\n Zmienne zostaną zastąpione rzeczywistą wartością.",
|
||||
"admin.config.email.reverse-share-subject": "Temat udziału odwrotnego",
|
||||
"admin.config.email.reverse-share-subject.description": "Temat wiadomości e-mail, która zostanie wysłana, gdy ktoś utworzy udostępnienie za pomocą linku udziału odwrotnego.",
|
||||
"admin.config.email.reverse-share-message": "Wiadomość udziału odwrotnego",
|
||||
"admin.config.email.reverse-share-message.description": "Wiadomość, która zostanie wysłana, gdy ktoś utworzy udział za pomocą twojego linku udziału odwrotnego. {shareUrl} zostanie zastąpiony nazwą twórcy i adresem URL udziału.",
|
||||
"admin.config.email.reset-password-subject": "Temat resetowania hasła",
|
||||
"admin.config.email.reset-password-subject.description": "Temat wiadomości e-mail, która zostanie wysłana, gdy użytkownik zażąda zresetowania hasła.",
|
||||
"admin.config.email.reset-password-message": "Wiadomość resetowania hasła",
|
||||
"admin.config.email.reset-password-message.description": "Wiadomość, która zostanie wysłana, gdy użytkownik zażąda zresetowania hasła. {url} zostanie zastąpiony adresem URL resetowania hasła.",
|
||||
"admin.config.email.invite-subject": "Temat zaproszenia",
|
||||
"admin.config.email.invite-subject.description": "Temat wiadomości e-mail, która zostanie wysłana, gdy administrator zaprasza użytkownika.",
|
||||
"admin.config.email.invite-message": "Wiadomość zaproszenia",
|
||||
"admin.config.email.invite-message.description": "Wiadomość, która zostanie wysłana, gdy administrator zaprasza użytkownika. {url} zostanie zastąpiony adresem URL zaproszenia, a {password} hasłem.",
|
||||
"admin.config.share.allow-registration": "Zezwól na rejestrację",
|
||||
"admin.config.share.allow-registration.description": "Czy dozwolona jest rejestracja",
|
||||
"admin.config.share.allow-unauthenticated-shares": "Zezwalaj na nieuwierzytelnione udostępnianie",
|
||||
"admin.config.share.allow-unauthenticated-shares.description": "Czy nieautoryzowani użytkownicy mogą tworzyć udostępnienia",
|
||||
"admin.config.share.max-size": "Rozmiar maksymalny",
|
||||
"admin.config.share.max-size.description": "Maksymalny rozmiar udziału w bajtach",
|
||||
"admin.config.share.zip-compression-level": "Poziom kompresji Zip",
|
||||
"admin.config.share.zip-compression-level.description": "Dostosuj poziom do równowagi między rozmiarem pliku a szybkością kompresji. Prawidłowe wartości mieszczą się w zakresie od 0 do 9, przy czym 0 to brak kompresji a 9 maksymalną kompresją. ",
|
||||
"admin.config.smtp.enabled": "Włączony",
|
||||
"admin.config.smtp.enabled.description": "Czy SMTP ma zostać włączony. Ustaw to, tylko jeśli wprowadziłeś host, port, e-mail, nazwę użytkownika i hasło serwera SMTP.",
|
||||
"admin.config.smtp.host": "Host",
|
||||
"admin.config.smtp.host.description": "Host serwera SMTP",
|
||||
"admin.config.smtp.port": "Numer portu",
|
||||
"admin.config.smtp.port.description": "Numer portu serwera SMTP",
|
||||
"admin.config.smtp.email": "Adres e-mail",
|
||||
"admin.config.smtp.email.description": "Adres e-mail, z którego e-maile są wysyłane",
|
||||
"admin.config.smtp.username": "Nazwa użytkownika",
|
||||
"admin.config.smtp.username.description": "Nazwa użytkownika serwera SMTP",
|
||||
"admin.config.smtp.password": "Hasło",
|
||||
"admin.config.smtp.password.description": "Hasło serwera SMTP",
|
||||
"admin.config.smtp.button.test": "Wyślij testowego e-maila",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Ups! Ta strona nie istnieje.",
|
||||
"404.button.home": "Wróć do strony domowej",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Zapisz",
|
||||
"common.button.create": "Utwórz",
|
||||
"common.button.submit": "Zgłoś",
|
||||
"common.button.delete": "Usuń",
|
||||
"common.button.cancel": "Anuluj",
|
||||
"common.button.confirm": "Potwierdź",
|
||||
"common.button.disable": "Wyłącz",
|
||||
"common.button.share": "Udostępnij",
|
||||
"common.button.generate": "Wygeneruj",
|
||||
"common.button.done": "Gotowe",
|
||||
"common.text.link": "Link",
|
||||
"common.text.or": "lub",
|
||||
"common.button.go-back": "Wróć",
|
||||
"common.notify.copied": "Link został skopiowany do schowka",
|
||||
"common.success": "Zakończono pomyślnie",
|
||||
"common.error": "Błąd",
|
||||
"common.error.unknown": "Wystąpił nieznany błąd",
|
||||
"common.error.invalid-email": "Adres e-mail jest nieprawidłowy",
|
||||
"common.error.too-short": "Musi mieć co najmniej {length} znaków",
|
||||
"common.error.too-long": "Musi mieć co najmniej {length} znaków",
|
||||
"common.error.exact-length": "Musi mieć dokładnie {length} znaków",
|
||||
"common.error.invalid-number": "Musi być liczbą",
|
||||
"common.error.field-required": "To pole jest wymagane"
|
||||
};
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "Iniciar sessão",
|
||||
"signIn.notify.totp-required.title": "Autenticação de dois fatores necessária",
|
||||
"signIn.notify.totp-required.description": "Insira seu código de autenticação de dois fatores",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Criar uma conta",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "Seu e-mail",
|
||||
"signup.button.submit": "Vamos começar",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Esqueceu a sua senha?",
|
||||
"resetPassword.description": "Insira o seu e-mail para redefinir a sua senha.",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "Senha",
|
||||
"account.card.password.old": "Senha antiga",
|
||||
"account.card.password.new": "Nova senha",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Senha alterada com sucesso",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Segurança",
|
||||
"account.card.security.totp.enable.description": "Digite a sua senha atual para começar a habilitar o TOTP",
|
||||
"account.card.security.totp.disable.description": "Digite a sua senha atual para desabilitar o TOTP",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "Compartilhamento",
|
||||
"admin.config.category.email": "E-mail",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "Nome da aplicação",
|
||||
"admin.config.general.app-name.description": "Nome da aplicação",
|
||||
"admin.config.general.app-url": "URL do Aplicativo",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "Senha",
|
||||
"admin.config.smtp.password.description": "Senha do servidor SMTP",
|
||||
"admin.config.smtp.button.test": "Enviar email de teste",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Ops, esta página não existe.",
|
||||
"404.button.home": "Me traga de volta para casa",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Salvar",
|
||||
"common.button.create": "Criar",
|
||||
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "Вход",
|
||||
"signIn.notify.totp-required.title": "Требуется двухфакторная аутентификация",
|
||||
"signIn.notify.totp-required.description": "Пожалуйста, введите код Вашей 2-х факторной аутентификации",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Создать аккаунт",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "Адрес эл. почты",
|
||||
"signup.button.submit": "Давайте начнём",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Забыли пароль?",
|
||||
"resetPassword.description": "Введите ваш email для восстановления пароля.",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "Пароль",
|
||||
"account.card.password.old": "Старый пароль",
|
||||
"account.card.password.new": "Новый пароль",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Пароль успешно изменён",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Безопасность",
|
||||
"account.card.security.totp.enable.description": "Введите ваш текущий пароль для начала включения TOTP",
|
||||
"account.card.security.totp.disable.description": "Введите ваш текущий пароль, чтобы отключить TOTP",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "Загрузки",
|
||||
"admin.config.category.email": "Электронная почта",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "Название приложения",
|
||||
"admin.config.general.app-name.description": "Видимое название приложения",
|
||||
"admin.config.general.app-url": "URL-адрес приложения",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "Пароль",
|
||||
"admin.config.smtp.password.description": "Пароль SMTP-сервера",
|
||||
"admin.config.smtp.button.test": "Отправить тестовое письмо",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Упс, этой страницы не существует.",
|
||||
"404.button.home": "Верните меня домой",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Сохранить",
|
||||
"common.button.create": "Создать",
|
||||
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "Пријавите се",
|
||||
"signIn.notify.totp-required.title": "Потребна је двофакторска аутентификација",
|
||||
"signIn.notify.totp-required.description": "Унесите свој двофакторски код за аутентификацију",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "Направи налог",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "Ваш и-мејл",
|
||||
"signup.button.submit": "Хајде да почнемо",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "Заборавили сте лозинку?",
|
||||
"resetPassword.description": "Унесите своју е-пошту да бисте ресетовали лозинку.",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "Лозинка",
|
||||
"account.card.password.old": "Стара лозинка",
|
||||
"account.card.password.new": "Нова лозинка",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "Лозинка је успешно промењена",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "Безбедност",
|
||||
"account.card.security.totp.enable.description": "Унесите своју тренутну лозинку да бисте почели да омогућавате ТОТП",
|
||||
"account.card.security.totp.disable.description": "Унесите своју тренутну лозинку да бисте онемогућили ТОТП",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "Дељење",
|
||||
"admin.config.category.email": "E-пошта",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "Назив апликације",
|
||||
"admin.config.general.app-name.description": "Назив апликације",
|
||||
"admin.config.general.app-url": "URL апликације",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "Лозинка",
|
||||
"admin.config.smtp.password.description": "Лозинка SMTP сервера",
|
||||
"admin.config.smtp.button.test": "Пошаљи тестну е-пошту",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "Опа - Ова страна не постоји.",
|
||||
"404.button.home": "Врати ме на почетак",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "Сачувај",
|
||||
"common.button.create": "Направи",
|
||||
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "เข้าสู่ระบบ",
|
||||
"signIn.notify.totp-required.title": "ยืนยันตรวจสอบสิทธิ์สองปัจจัย",
|
||||
"signIn.notify.totp-required.description": "กรุณาใส่รหัสยืนยันตัวตนสองปัจจัย",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "สมัครบัญชี",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "อีเมล์์์์์์์์ของคุณ",
|
||||
"signup.button.submit": "เริ่มต้นกัน",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "ลืมรหัสผ่าน?",
|
||||
"resetPassword.description": "กรุณาใส่อีเมล์์์์์์์ของคุณเพื่อรีเซ็ตรหัสผ่าน",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "รหัสผ่าน",
|
||||
"account.card.password.old": "รหัสผ่านเก่า",
|
||||
"account.card.password.new": "รหัสผ่านใหม่",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "อัปเดตรหัสผ่านเรียบร้อยแล้ว",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "ความปลอดภัย",
|
||||
"account.card.security.totp.enable.description": "ใส่รหัสผ่านปัจจุบันของคุณเพื่อเริ่มต้นการเปิดใช้งาน TOTP",
|
||||
"account.card.security.totp.disable.description": "ใส่รหัสผ่านปัจจุบันของคุณเพื่อปิดใช้งาน TOTP",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "การแชร์",
|
||||
"admin.config.category.email": "อีเมล์์์์์์",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "ชื่อแอพ",
|
||||
"admin.config.general.app-name.description": "ชื่อแอพพลิเคชัน",
|
||||
"admin.config.general.app-url": "URL ของแอพ",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "รหัสผ่าน",
|
||||
"admin.config.smtp.password.description": "รหัสผ่านของเซิร์ฟเวอร์ SMTP",
|
||||
"admin.config.smtp.button.test": "ส่งอีเมล์์์์์์ทดสอบ",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "ไม่พบหน้าที่คุณกำลังมองหา",
|
||||
"404.button.home": "หน้าแรก",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "บันทึก",
|
||||
"common.button.create": "สร้าง",
|
||||
|
||||
@@ -33,6 +33,12 @@ export default {
|
||||
"signin.button.submit": "登录",
|
||||
"signIn.notify.totp-required.title": "请继续两步验证",
|
||||
"signIn.notify.totp-required.description": "请输入一次性验证码",
|
||||
"signIn.oauth.or": "OR",
|
||||
"signIn.oauth.github": "GitHub",
|
||||
"signIn.oauth.google": "Google",
|
||||
"signIn.oauth.microsoft": "Microsoft",
|
||||
"signIn.oauth.discord": "Discord",
|
||||
"signIn.oauth.oidc": "OpenID",
|
||||
// END /auth/signin
|
||||
// /auth/signup
|
||||
"signup.title": "创建账户",
|
||||
@@ -44,6 +50,10 @@ export default {
|
||||
"signup.input.email.placeholder": "请输入电子邮件",
|
||||
"signup.button.submit": "注册",
|
||||
// END /auth/signup
|
||||
// /auth/totp
|
||||
"totp.title": "TOTP Authentication",
|
||||
"totp.button.signIn": "Sign in",
|
||||
// END /auth/totp
|
||||
// /auth/reset-password
|
||||
"resetPassword.title": "忘记密码?",
|
||||
"resetPassword.description": "请输入电子邮件接受重置密码邮件",
|
||||
@@ -62,7 +72,20 @@ export default {
|
||||
"account.card.password.title": "密码",
|
||||
"account.card.password.old": "旧密码",
|
||||
"account.card.password.new": "新密码",
|
||||
"account.card.password.noPasswordSet": "You don't have a password set. If you want to sign in with email and password you need to set a password.",
|
||||
"account.notify.password.success": "密码更改成功!",
|
||||
"account.card.oauth.title": "Social login",
|
||||
"account.card.oauth.github": "GitHub",
|
||||
"account.card.oauth.google": "Google",
|
||||
"account.card.oauth.microsoft": "Microsoft",
|
||||
"account.card.oauth.discord": "Discord",
|
||||
"account.card.oauth.oidc": "OpenID",
|
||||
"account.card.oauth.link": "Link",
|
||||
"account.card.oauth.unlink": "Unlink",
|
||||
"account.card.oauth.unlinked": "Unlinked",
|
||||
"account.modal.unlink.title": "Unlink account",
|
||||
"account.modal.unlink.description": "Unlinking your social accounts may cause you to lose your account if you don't remember your username and password.",
|
||||
"account.notify.oauth.unlinked.success": "Unlinked successfully",
|
||||
"account.card.security.title": "安全",
|
||||
"account.card.security.totp.enable.description": "请输入当前密码开启两步验证",
|
||||
"account.card.security.totp.disable.description": "请输入当前密码关闭两步验证",
|
||||
@@ -246,6 +269,7 @@ export default {
|
||||
"admin.config.category.share": "共享",
|
||||
"admin.config.category.email": "电子邮件",
|
||||
"admin.config.category.smtp": "SMTP",
|
||||
"admin.config.category.oauth": "Social Login",
|
||||
"admin.config.general.app-name": "App 名称",
|
||||
"admin.config.general.app-name.description": "这个 App 的名称",
|
||||
"admin.config.general.app-url": "App 的地址",
|
||||
@@ -294,9 +318,63 @@ export default {
|
||||
"admin.config.smtp.password": "密码",
|
||||
"admin.config.smtp.password.description": "SMTP 主机密码",
|
||||
"admin.config.smtp.button.test": "发送测试邮件",
|
||||
"admin.config.oauth.allow-registration": "Allow registration",
|
||||
"admin.config.oauth.allow-registration.description": "Allow users to register via social login",
|
||||
"admin.config.oauth.ignore-totp": "Ignore TOTP",
|
||||
"admin.config.oauth.ignore-totp.description": "Whether to ignore TOTP when user using social login",
|
||||
"admin.config.oauth.github-enabled": "GitHub",
|
||||
"admin.config.oauth.github-enabled.description": "Whether GitHub login is enabled",
|
||||
"admin.config.oauth.github-client-id": "GitHub Client ID",
|
||||
"admin.config.oauth.github-client-id.description": "Client ID of the GitHub OAuth app",
|
||||
"admin.config.oauth.github-client-secret": "GitHub Client secret",
|
||||
"admin.config.oauth.github-client-secret.description": "Client secret of the GitHub OAuth app",
|
||||
"admin.config.oauth.google-enabled": "Google",
|
||||
"admin.config.oauth.google-enabled.description": "Whether Google login is enabled",
|
||||
"admin.config.oauth.google-client-id": "Google Client ID",
|
||||
"admin.config.oauth.google-client-id.description": "Client ID of the Google OAuth app",
|
||||
"admin.config.oauth.google-client-secret": "Google Client secret",
|
||||
"admin.config.oauth.google-client-secret.description": "Client secret of the Google OAuth app",
|
||||
"admin.config.oauth.microsoft-enabled": "Microsoft",
|
||||
"admin.config.oauth.microsoft-enabled.description": "Whether Microsoft login is enabled",
|
||||
"admin.config.oauth.microsoft-tenant": "Microsoft Tenant",
|
||||
"admin.config.oauth.microsoft-tenant.description": "Tenant ID of the Microsoft OAuth app\ncommon: Users with both a personal Microsoft account and a work or school account from Microsoft Entra ID can sign in to the application. organizations: Only users with work or school accounts from Microsoft Entra ID can sign in to the application.\nconsumers: Only users with a personal Microsoft account can sign in to the application.\ndomain name of the Microsoft Entra tenant or the tenant ID in GUID format: Only users from a specific Microsoft Entra tenant (directory members with a work or school account or directory guests with a personal Microsoft account) can sign in to the application.",
|
||||
"admin.config.oauth.microsoft-client-id": "Microsoft Client ID",
|
||||
"admin.config.oauth.microsoft-client-id.description": "Client ID of the Microsoft OAuth app",
|
||||
"admin.config.oauth.microsoft-client-secret": "Microsoft Client secret",
|
||||
"admin.config.oauth.microsoft-client-secret.description": "Client secret of the Microsoft OAuth app",
|
||||
"admin.config.oauth.discord-enabled": "Discord",
|
||||
"admin.config.oauth.discord-enabled.description": "Whether Discord login is enabled",
|
||||
"admin.config.oauth.discord-client-id": "Discord Client ID",
|
||||
"admin.config.oauth.discord-client-id.description": "Client ID of the Discord OAuth app",
|
||||
"admin.config.oauth.discord-client-secret": "Discord Client secret",
|
||||
"admin.config.oauth.discord-client-secret.description": "Client secret of the Discord OAuth app",
|
||||
"admin.config.oauth.oidc-enabled": "OpenID",
|
||||
"admin.config.oauth.oidc-enabled.description": "Whether OpenID login is enabled",
|
||||
"admin.config.oauth.oidc-discovery-uri": "OpenID Discovery URI",
|
||||
"admin.config.oauth.oidc-discovery-uri.description": "Discovery URI of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-id": "OpenID Client ID",
|
||||
"admin.config.oauth.oidc-client-id.description": "Client ID of the OpenID OAuth app",
|
||||
"admin.config.oauth.oidc-client-secret": "OpenID Client secret",
|
||||
"admin.config.oauth.oidc-client-secret.description": "Client secret of the OpenID OAuth app",
|
||||
// 404
|
||||
"404.description": "当前的页面走丢啦",
|
||||
"404.button.home": "返回主页",
|
||||
// error
|
||||
"error.title": "Error",
|
||||
"error.description": "Oops!",
|
||||
"error.button.back": "Go back",
|
||||
"error.msg.default": "Something went wrong.",
|
||||
"error.msg.access_denied": "You canceled the authentication process, please try again.",
|
||||
"error.msg.expired_token": "The authentication process took too long, please try again.",
|
||||
"error.msg.no_user": "User linked to this {0} account doesn't exist.",
|
||||
"error.msg.no_email": "Can't get email address from this {0} account.",
|
||||
"error.msg.already_linked": "This {0} account is already linked to another account.",
|
||||
"error.msg.not_linked": "This {0} account haven't linked to any account yet.",
|
||||
"error.param.provider_github": "GitHub",
|
||||
"error.param.provider_google": "Google",
|
||||
"error.param.provider_microsoft": "Microsoft",
|
||||
"error.param.provider_discord": "Discord",
|
||||
"error.param.provider_oidc": "OpenID",
|
||||
// Common translations
|
||||
"common.button.save": "保存",
|
||||
"common.button.create": "创建",
|
||||
|
||||
@@ -14,7 +14,7 @@ export const config = {
|
||||
export async function middleware(request: NextRequest) {
|
||||
const routes = {
|
||||
unauthenticated: new Routes(["/auth/*", "/"]),
|
||||
public: new Routes(["/share/*", "/s/*", "/upload/*"]),
|
||||
public: new Routes(["/share/*", "/s/*", "/upload/*", "/error"]),
|
||||
admin: new Routes(["/admin/*"]),
|
||||
account: new Routes(["/account*"]),
|
||||
disabled: new Routes([]),
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "@mantine/core";
|
||||
import { useForm, yupResolver } from "@mantine/form";
|
||||
import { useModals } from "@mantine/modals";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Tb2Fa } from "react-icons/tb";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
import * as yup from "yup";
|
||||
@@ -20,16 +21,28 @@ import Meta from "../../components/Meta";
|
||||
import LanguagePicker from "../../components/account/LanguagePicker";
|
||||
import ThemeSwitcher from "../../components/account/ThemeSwitcher";
|
||||
import showEnableTotpModal from "../../components/account/showEnableTotpModal";
|
||||
import useConfig from "../../hooks/config.hook";
|
||||
import useTranslate from "../../hooks/useTranslate.hook";
|
||||
import useUser from "../../hooks/user.hook";
|
||||
import authService from "../../services/auth.service";
|
||||
import userService from "../../services/user.service";
|
||||
import { getOAuthIcon, getOAuthUrl, unlinkOAuth } from "../../utils/oauth.util";
|
||||
import toast from "../../utils/toast.util";
|
||||
|
||||
const Account = () => {
|
||||
const [oauth, setOAuth] = useState<string[]>([]);
|
||||
const [oauthStatus, setOAuthStatus] = useState<Record<
|
||||
string,
|
||||
{
|
||||
provider: string;
|
||||
providerUsername: string;
|
||||
}
|
||||
> | null>(null);
|
||||
|
||||
const { user, refreshUser } = useUser();
|
||||
const modals = useModals();
|
||||
const t = useTranslate();
|
||||
const config = useConfig();
|
||||
|
||||
const accountForm = useForm({
|
||||
initialValues: {
|
||||
@@ -53,10 +66,14 @@ const Account = () => {
|
||||
},
|
||||
validate: yupResolver(
|
||||
yup.object().shape({
|
||||
oldPassword: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
.required(t("common.error.field-required")),
|
||||
oldPassword: yup.string().when([], {
|
||||
is: () => !!user?.hasPassword,
|
||||
then: (schema) =>
|
||||
schema
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
.required(t("common.error.field-required")),
|
||||
otherwise: (schema) => schema.notRequired(),
|
||||
}),
|
||||
password: yup
|
||||
.string()
|
||||
.min(8, t("common.error.too-short", { length: 8 }))
|
||||
@@ -96,6 +113,25 @@ const Account = () => {
|
||||
),
|
||||
});
|
||||
|
||||
const refreshOAuthStatus = () => {
|
||||
authService
|
||||
.getOAuthStatus()
|
||||
.then((data) => {
|
||||
setOAuthStatus(data.data);
|
||||
})
|
||||
.catch(toast.axiosError);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
authService
|
||||
.getAvailableOAuth()
|
||||
.then((data) => {
|
||||
setOAuth(data.data);
|
||||
})
|
||||
.catch(toast.axiosError);
|
||||
refreshOAuthStatus();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title={t("account.title")} />
|
||||
@@ -143,7 +179,8 @@ const Account = () => {
|
||||
onSubmit={passwordForm.onSubmit((values) =>
|
||||
authService
|
||||
.updatePassword(values.oldPassword, values.password)
|
||||
.then(() => {
|
||||
.then(async () => {
|
||||
refreshUser();
|
||||
toast.success(t("account.notify.password.success"));
|
||||
passwordForm.reset();
|
||||
})
|
||||
@@ -151,10 +188,16 @@ const Account = () => {
|
||||
)}
|
||||
>
|
||||
<Stack>
|
||||
<PasswordInput
|
||||
label={t("account.card.password.old")}
|
||||
{...passwordForm.getInputProps("oldPassword")}
|
||||
/>
|
||||
{user?.hasPassword ? (
|
||||
<PasswordInput
|
||||
label={t("account.card.password.old")}
|
||||
{...passwordForm.getInputProps("oldPassword")}
|
||||
/>
|
||||
) : (
|
||||
<Text size="sm" color="dimmed">
|
||||
<FormattedMessage id="account.card.password.noPasswordSet" />
|
||||
</Text>
|
||||
)}
|
||||
<PasswordInput
|
||||
label={t("account.card.password.new")}
|
||||
{...passwordForm.getInputProps("password")}
|
||||
@@ -167,7 +210,79 @@ const Account = () => {
|
||||
</Stack>
|
||||
</form>
|
||||
</Paper>
|
||||
{oauth.length > 0 && (
|
||||
<Paper withBorder p="xl" mt="lg">
|
||||
<Title order={5} mb="xs">
|
||||
<FormattedMessage id="account.card.oauth.title" />
|
||||
</Title>
|
||||
|
||||
<Tabs defaultValue={oauth[0] || ""}>
|
||||
<Tabs.List>
|
||||
{oauth.map((provider) => (
|
||||
<Tabs.Tab
|
||||
value={provider}
|
||||
icon={getOAuthIcon(provider)}
|
||||
key={provider}
|
||||
>
|
||||
{t(`account.card.oauth.${provider}`)}
|
||||
</Tabs.Tab>
|
||||
))}
|
||||
</Tabs.List>
|
||||
{oauth.map((provider) => (
|
||||
<Tabs.Panel value={provider} pt="xs" key={provider}>
|
||||
<Group position="apart">
|
||||
<Text>
|
||||
{oauthStatus?.[provider]
|
||||
? oauthStatus[provider].providerUsername
|
||||
: t("account.card.oauth.unlinked")}
|
||||
</Text>
|
||||
{oauthStatus?.[provider] ? (
|
||||
<Button
|
||||
onClick={() => {
|
||||
modals.openConfirmModal({
|
||||
title: t("account.modal.unlink.title"),
|
||||
children: (
|
||||
<Text>
|
||||
{t("account.modal.unlink.description")}
|
||||
</Text>
|
||||
),
|
||||
labels: {
|
||||
confirm: t("account.card.oauth.unlink"),
|
||||
cancel: t("common.button.cancel"),
|
||||
},
|
||||
confirmProps: { color: "red" },
|
||||
onConfirm: () => {
|
||||
unlinkOAuth(provider)
|
||||
.then(() => {
|
||||
toast.success(
|
||||
t("account.notify.oauth.unlinked.success"),
|
||||
);
|
||||
refreshOAuthStatus();
|
||||
})
|
||||
.catch(toast.axiosError);
|
||||
},
|
||||
});
|
||||
}}
|
||||
>
|
||||
{t("account.card.oauth.unlink")}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
component="a"
|
||||
href={getOAuthUrl(
|
||||
config.get("general.appUrl"),
|
||||
provider,
|
||||
)}
|
||||
>
|
||||
{t("account.card.oauth.link")}
|
||||
</Button>
|
||||
)}
|
||||
</Group>
|
||||
</Tabs.Panel>
|
||||
))}
|
||||
</Tabs>
|
||||
</Paper>
|
||||
)}
|
||||
<Paper withBorder p="xl" mt="lg">
|
||||
<Title order={5} mb="xs">
|
||||
<FormattedMessage id="account.card.security.title" />
|
||||
|
||||
@@ -24,10 +24,7 @@ import CenterLoader from "../../../components/core/CenterLoader";
|
||||
import useConfig from "../../../hooks/config.hook";
|
||||
import configService from "../../../services/config.service";
|
||||
import { AdminConfig, UpdateConfig } from "../../../types/config.type";
|
||||
import {
|
||||
camelToKebab,
|
||||
capitalizeFirstLetter,
|
||||
} from "../../../utils/string.util";
|
||||
import { camelToKebab } from "../../../utils/string.util";
|
||||
import toast from "../../../utils/toast.util";
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
|
||||
@@ -128,7 +125,7 @@ export default function AppShellDemo() {
|
||||
<>
|
||||
<Stack>
|
||||
<Title mb="md" order={3}>
|
||||
{capitalizeFirstLetter(categoryId)}
|
||||
{t("admin.config.category." + categoryId)}
|
||||
</Title>
|
||||
{configVariables.map((configVariable) => (
|
||||
<Group key={configVariable.key} position="apart">
|
||||
|
||||
@@ -11,6 +11,8 @@ export const config = {
|
||||
|
||||
const { apiURL } = getConfig().serverRuntimeConfig;
|
||||
|
||||
// A proxy to the API server only used in development.
|
||||
// In production this route gets overridden by nginx.
|
||||
export default (req: NextApiRequest, res: NextApiResponse) => {
|
||||
httpProxyMiddleware(req, res, {
|
||||
headers: {
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
import axios from "axios";
|
||||
import { NextApiRequest, NextApiResponse } from "next";
|
||||
import getConfig from "next/config";
|
||||
|
||||
const { apiURL } = getConfig().serverRuntimeConfig;
|
||||
|
||||
export default async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
const apiStatus = await axios
|
||||
.get(`${apiURL}/api/configs`)
|
||||
.then(() => "OK")
|
||||
.catch(() => "ERROR");
|
||||
|
||||
res.status(apiStatus == "OK" ? 200 : 500).send(apiStatus);
|
||||
};
|
||||
18
frontend/src/pages/auth/totp/[loginToken].tsx
Normal file
18
frontend/src/pages/auth/totp/[loginToken].tsx
Normal file
@@ -0,0 +1,18 @@
|
||||
import useTranslate from "../../../hooks/useTranslate.hook";
|
||||
import Meta from "../../../components/Meta";
|
||||
import TotpForm from "../../../components/auth/TotpForm";
|
||||
import { useRouter } from "next/router";
|
||||
|
||||
const Totp = () => {
|
||||
const t = useTranslate();
|
||||
const router = useRouter();
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title={t("totp.title")} />
|
||||
<TotpForm redirectPath={(router.query.redirect as string) || "/upload"} />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default Totp;
|
||||
49
frontend/src/pages/error.tsx
Normal file
49
frontend/src/pages/error.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from "react";
|
||||
import { Button, createStyles, Stack, Text, Title } from "@mantine/core";
|
||||
import Meta from "../components/Meta";
|
||||
import useTranslate from "../hooks/useTranslate.hook";
|
||||
import { useRouter } from "next/router";
|
||||
import { FormattedMessage } from "react-intl";
|
||||
|
||||
const useStyle = createStyles({
|
||||
title: {
|
||||
fontSize: 100,
|
||||
},
|
||||
});
|
||||
|
||||
export default function Error() {
|
||||
const { classes } = useStyle();
|
||||
const t = useTranslate();
|
||||
const router = useRouter();
|
||||
|
||||
const params = router.query.params
|
||||
? (router.query.params as string).split(",").map((param) => {
|
||||
return t(`error.param.${param}`);
|
||||
})
|
||||
: [];
|
||||
|
||||
return (
|
||||
<>
|
||||
<Meta title={t("error.title")} />
|
||||
<Stack align="center">
|
||||
<Title order={3} className={classes.title}>
|
||||
{t("error.description")}
|
||||
</Title>
|
||||
<Text mt="xl" size="lg">
|
||||
<FormattedMessage
|
||||
id={`error.msg.${router.query.error || "default"}`}
|
||||
values={Object.fromEntries(
|
||||
[params].map((value, key) => [key.toString(), value]),
|
||||
)}
|
||||
/>
|
||||
</Text>
|
||||
<Button
|
||||
mt="xl"
|
||||
onClick={() => router.push((router.query.redirect as string) || "/")}
|
||||
>
|
||||
{t("error.button.back")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -56,7 +56,7 @@ const Upload = ({
|
||||
file.uploadingProgress = progress;
|
||||
}
|
||||
return file;
|
||||
})
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -84,7 +84,7 @@ const Upload = ({
|
||||
name: file.name,
|
||||
},
|
||||
chunkIndex,
|
||||
chunks
|
||||
chunks,
|
||||
)
|
||||
.then((response) => {
|
||||
fileId = response.id;
|
||||
@@ -114,7 +114,7 @@ const Upload = ({
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
Promise.all(fileUploadPromises);
|
||||
@@ -129,19 +129,19 @@ const Upload = ({
|
||||
isReverseShare,
|
||||
appUrl: config.get("general.appUrl"),
|
||||
allowUnauthenticatedShares: config.get(
|
||||
"share.allowUnauthenticatedShares"
|
||||
"share.allowUnauthenticatedShares",
|
||||
),
|
||||
enableEmailRecepients: config.get("email.enableShareEmailRecipients"),
|
||||
},
|
||||
files,
|
||||
uploadFiles
|
||||
uploadFiles,
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// Check if there are any files that failed to upload
|
||||
const fileErrorCount = files.filter(
|
||||
(file) => file.uploadingProgress == -1
|
||||
(file) => file.uploadingProgress == -1,
|
||||
).length;
|
||||
|
||||
if (fileErrorCount > 0) {
|
||||
@@ -151,7 +151,7 @@ const Upload = ({
|
||||
{
|
||||
withCloseButton: false,
|
||||
autoClose: false,
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
errorToastShown = true;
|
||||
|
||||
@@ -15,24 +15,11 @@ const signIn = async (emailOrUsername: string, password: string) => {
|
||||
return response;
|
||||
};
|
||||
|
||||
const signInTotp = async (
|
||||
emailOrUsername: string,
|
||||
password: string,
|
||||
totp: string,
|
||||
loginToken: string,
|
||||
) => {
|
||||
const emailOrUsernameBody = emailOrUsername.includes("@")
|
||||
? { email: emailOrUsername }
|
||||
: { username: emailOrUsername };
|
||||
|
||||
const response = await api.post("auth/signIn/totp", {
|
||||
...emailOrUsernameBody,
|
||||
password,
|
||||
const signInTotp = (totp: string, loginToken: string) => {
|
||||
return api.post("auth/signIn/totp", {
|
||||
totp,
|
||||
loginToken,
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
const signUp = async (email: string, username: string, password: string) => {
|
||||
@@ -96,6 +83,14 @@ const disableTOTP = async (totpCode: string, password: string) => {
|
||||
});
|
||||
};
|
||||
|
||||
const getAvailableOAuth = async () => {
|
||||
return api.get("/oauth/available");
|
||||
};
|
||||
|
||||
const getOAuthStatus = () => {
|
||||
return api.get("/oauth/status");
|
||||
};
|
||||
|
||||
export default {
|
||||
signIn,
|
||||
signInTotp,
|
||||
@@ -108,4 +103,6 @@ export default {
|
||||
enableTOTP,
|
||||
verifyTOTP,
|
||||
disableTOTP,
|
||||
getAvailableOAuth,
|
||||
getOAuthStatus,
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ type User = {
|
||||
email: string;
|
||||
isAdmin: boolean;
|
||||
totpVerified: boolean;
|
||||
hasPassword: boolean;
|
||||
};
|
||||
|
||||
export type CreateUser = {
|
||||
|
||||
29
frontend/src/utils/oauth.util.tsx
Normal file
29
frontend/src/utils/oauth.util.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import {
|
||||
SiDiscord,
|
||||
SiGithub,
|
||||
SiGoogle,
|
||||
SiMicrosoft,
|
||||
SiOpenid,
|
||||
} from "react-icons/si";
|
||||
import React from "react";
|
||||
import api from "../services/api.service";
|
||||
|
||||
const getOAuthUrl = (appUrl: string, provider: string) => {
|
||||
return `${appUrl}/api/oauth/auth/${provider}`;
|
||||
};
|
||||
|
||||
const getOAuthIcon = (provider: string) => {
|
||||
return {
|
||||
google: <SiGoogle />,
|
||||
microsoft: <SiMicrosoft />,
|
||||
github: <SiGithub />,
|
||||
discord: <SiDiscord />,
|
||||
oidc: <SiOpenid />,
|
||||
}[provider];
|
||||
};
|
||||
|
||||
const unlinkOAuth = (provider: string) => {
|
||||
return api.post(`/oauth/unlink/${provider}`);
|
||||
};
|
||||
|
||||
export { getOAuthUrl, getOAuthIcon, unlinkOAuth };
|
||||
22
nginx/nginx.conf
Normal file
22
nginx/nginx.conf
Normal file
@@ -0,0 +1,22 @@
|
||||
events {}
|
||||
|
||||
http {
|
||||
server {
|
||||
listen 3000;
|
||||
client_max_body_size 100M;
|
||||
|
||||
location /api {
|
||||
proxy_pass http://localhost:8080;
|
||||
proxy_set_header X-Forwarded-Host $host:$server_port;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:3333;
|
||||
proxy_set_header X-Forwarded-Host $host:$server_port;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "pingvin-share",
|
||||
"version": "0.18.0",
|
||||
"version": "0.19.0",
|
||||
"scripts": {
|
||||
"format": "cd frontend && npm run format && cd ../backend && npm run format",
|
||||
"lint": "cd frontend && npm run lint && cd ../backend && npm run lint",
|
||||
|
||||
Reference in New Issue
Block a user