oauth2 revokations
This commit is contained in:
parent
88f19163c6
commit
c35cb362ff
219
package-lock.json
generated
219
package-lock.json
generated
@ -16,8 +16,11 @@
|
|||||||
"@nestjs/serve-static": "^2.2.2",
|
"@nestjs/serve-static": "^2.2.2",
|
||||||
"@nestjs/throttler": "^2.0.1",
|
"@nestjs/throttler": "^2.0.1",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
|
"body-parser": "^1.19.2",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
|
"connect-redis": "^6.1.3",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"cropperjs": "^1.5.12",
|
"cropperjs": "^1.5.12",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"express-session": "^1.17.2",
|
"express-session": "^1.17.2",
|
||||||
@ -30,6 +33,7 @@
|
|||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"qrcode": "^1.5.0",
|
"qrcode": "^1.5.0",
|
||||||
|
"redis": "^3.1.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.2.0",
|
"rxjs": "^7.2.0",
|
||||||
@ -44,6 +48,8 @@
|
|||||||
"@nestjs/schematics": "^8.0.0",
|
"@nestjs/schematics": "^8.0.0",
|
||||||
"@nestjs/testing": "^8.0.0",
|
"@nestjs/testing": "^8.0.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
|
"@types/connect-redis": "^0.0.18",
|
||||||
|
"@types/csurf": "^1.11.2",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/express-session": "^1.17.4",
|
"@types/express-session": "^1.17.4",
|
||||||
"@types/jest": "27.4.1",
|
"@types/jest": "27.4.1",
|
||||||
@ -3129,12 +3135,33 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/connect-redis": {
|
||||||
|
"version": "0.0.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/connect-redis/-/connect-redis-0.0.18.tgz",
|
||||||
|
"integrity": "sha512-iGygGbXgPIr94DEAuoluWhzre3c2/ew5NPlbW9IWvwCTXMM1YCmc7M9wpXMkYqt6kB9aO1sjZnmDzyugUu+2vQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express": "*",
|
||||||
|
"@types/express-session": "*",
|
||||||
|
"@types/ioredis": "*",
|
||||||
|
"@types/redis": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/cookiejar": {
|
"node_modules/@types/cookiejar": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
||||||
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/csurf": {
|
||||||
|
"version": "1.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/csurf/-/csurf-1.11.2.tgz",
|
||||||
|
"integrity": "sha512-9bc98EnwmC1S0aSJiA8rWwXtgXtXHHOQOsGHptImxFgqm6CeH+mIOunHRg6+/eg2tlmDMX3tY7XrWxo2M/nUNQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/express-serve-static-core": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/eslint": {
|
"node_modules/@types/eslint": {
|
||||||
"version": "8.4.1",
|
"version": "8.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||||
@ -3202,6 +3229,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/ioredis": {
|
||||||
|
"version": "4.28.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.28.10.tgz",
|
||||||
|
"integrity": "sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/istanbul-lib-coverage": {
|
"node_modules/@types/istanbul-lib-coverage": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
|
||||||
@ -3326,6 +3362,15 @@
|
|||||||
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/redis": {
|
||||||
|
"version": "2.8.32",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.32.tgz",
|
||||||
|
"integrity": "sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/serve-static": {
|
"node_modules/@types/serve-static": {
|
||||||
"version": "1.13.10",
|
"version": "1.13.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
|
||||||
@ -4986,6 +5031,14 @@
|
|||||||
"safe-buffer": "~5.1.0"
|
"safe-buffer": "~5.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/connect-redis": {
|
||||||
|
"version": "6.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.1.3.tgz",
|
||||||
|
"integrity": "sha512-aaNluLlAn/3JPxRwdzw7lhvEoU6Enb+d83xnokUNhC9dktqBoawKWL+WuxinxvBLTz6q9vReTnUDnUslaz74aw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/consola": {
|
"node_modules/consola": {
|
||||||
"version": "2.15.3",
|
"version": "2.15.3",
|
||||||
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
|
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
|
||||||
@ -5047,6 +5100,26 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/cookie-parser": {
|
||||||
|
"version": "1.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||||
|
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": "0.4.1",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/cookie-parser/node_modules/cookie": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/cookie-signature": {
|
"node_modules/cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
@ -9982,6 +10055,56 @@
|
|||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redis": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
|
||||||
|
"dependencies": {
|
||||||
|
"denque": "^1.5.0",
|
||||||
|
"redis-commands": "^1.7.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/node-redis"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redis-commands": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
|
||||||
|
},
|
||||||
|
"node_modules/redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
|
||||||
|
"dependencies": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/redis/node_modules/denque": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect-metadata": {
|
"node_modules/reflect-metadata": {
|
||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||||
@ -14606,12 +14729,33 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/connect-redis": {
|
||||||
|
"version": "0.0.18",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/connect-redis/-/connect-redis-0.0.18.tgz",
|
||||||
|
"integrity": "sha512-iGygGbXgPIr94DEAuoluWhzre3c2/ew5NPlbW9IWvwCTXMM1YCmc7M9wpXMkYqt6kB9aO1sjZnmDzyugUu+2vQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/express": "*",
|
||||||
|
"@types/express-session": "*",
|
||||||
|
"@types/ioredis": "*",
|
||||||
|
"@types/redis": "^2.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/cookiejar": {
|
"@types/cookiejar": {
|
||||||
"version": "2.1.2",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
||||||
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/csurf": {
|
||||||
|
"version": "1.11.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/csurf/-/csurf-1.11.2.tgz",
|
||||||
|
"integrity": "sha512-9bc98EnwmC1S0aSJiA8rWwXtgXtXHHOQOsGHptImxFgqm6CeH+mIOunHRg6+/eg2tlmDMX3tY7XrWxo2M/nUNQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/express-serve-static-core": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/eslint": {
|
"@types/eslint": {
|
||||||
"version": "8.4.1",
|
"version": "8.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||||
@ -14679,6 +14823,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/ioredis": {
|
||||||
|
"version": "4.28.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/ioredis/-/ioredis-4.28.10.tgz",
|
||||||
|
"integrity": "sha512-69LyhUgrXdgcNDv7ogs1qXZomnfOEnSmrmMFqKgt1XMJxmoOSG/u3wYy13yACIfKuMJ8IhKgHafDO3sx19zVQQ==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/istanbul-lib-coverage": {
|
"@types/istanbul-lib-coverage": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
|
||||||
@ -14803,6 +14956,15 @@
|
|||||||
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/redis": {
|
||||||
|
"version": "2.8.32",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/redis/-/redis-2.8.32.tgz",
|
||||||
|
"integrity": "sha512-7jkMKxcGq9p242exlbsVzuJb57KqHRhNl4dHoQu2Y5v9bCAbtIXXH0R3HleSQW4CTOqpHIYUW3t6tpUj4BVQ+w==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/serve-static": {
|
"@types/serve-static": {
|
||||||
"version": "1.13.10",
|
"version": "1.13.10",
|
||||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
|
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
|
||||||
@ -16077,6 +16239,11 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"connect-redis": {
|
||||||
|
"version": "6.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/connect-redis/-/connect-redis-6.1.3.tgz",
|
||||||
|
"integrity": "sha512-aaNluLlAn/3JPxRwdzw7lhvEoU6Enb+d83xnokUNhC9dktqBoawKWL+WuxinxvBLTz6q9vReTnUDnUslaz74aw=="
|
||||||
|
},
|
||||||
"consola": {
|
"consola": {
|
||||||
"version": "2.15.3",
|
"version": "2.15.3",
|
||||||
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
|
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
|
||||||
@ -16131,6 +16298,22 @@
|
|||||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz",
|
||||||
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
|
"integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA=="
|
||||||
},
|
},
|
||||||
|
"cookie-parser": {
|
||||||
|
"version": "1.4.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz",
|
||||||
|
"integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==",
|
||||||
|
"requires": {
|
||||||
|
"cookie": "0.4.1",
|
||||||
|
"cookie-signature": "1.0.6"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cookie": {
|
||||||
|
"version": "0.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz",
|
||||||
|
"integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"cookie-signature": {
|
"cookie-signature": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||||
@ -19903,6 +20086,42 @@
|
|||||||
"resolve": "^1.1.6"
|
"resolve": "^1.1.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redis": {
|
||||||
|
"version": "3.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz",
|
||||||
|
"integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==",
|
||||||
|
"requires": {
|
||||||
|
"denque": "^1.5.0",
|
||||||
|
"redis-commands": "^1.7.0",
|
||||||
|
"redis-errors": "^1.2.0",
|
||||||
|
"redis-parser": "^3.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"denque": {
|
||||||
|
"version": "1.5.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz",
|
||||||
|
"integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"redis-commands": {
|
||||||
|
"version": "1.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz",
|
||||||
|
"integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ=="
|
||||||
|
},
|
||||||
|
"redis-errors": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz",
|
||||||
|
"integrity": "sha1-62LSrbFeTq9GEMBK/hUpOEJQq60="
|
||||||
|
},
|
||||||
|
"redis-parser": {
|
||||||
|
"version": "3.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz",
|
||||||
|
"integrity": "sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=",
|
||||||
|
"requires": {
|
||||||
|
"redis-errors": "^1.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"reflect-metadata": {
|
"reflect-metadata": {
|
||||||
"version": "0.1.13",
|
"version": "0.1.13",
|
||||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||||
|
@ -30,8 +30,11 @@
|
|||||||
"@nestjs/serve-static": "^2.2.2",
|
"@nestjs/serve-static": "^2.2.2",
|
||||||
"@nestjs/throttler": "^2.0.1",
|
"@nestjs/throttler": "^2.0.1",
|
||||||
"bcrypt": "^5.0.1",
|
"bcrypt": "^5.0.1",
|
||||||
|
"body-parser": "^1.19.2",
|
||||||
"class-transformer": "^0.5.1",
|
"class-transformer": "^0.5.1",
|
||||||
"class-validator": "^0.13.2",
|
"class-validator": "^0.13.2",
|
||||||
|
"connect-redis": "^6.1.3",
|
||||||
|
"cookie-parser": "^1.4.6",
|
||||||
"cropperjs": "^1.5.12",
|
"cropperjs": "^1.5.12",
|
||||||
"dotenv": "^16.0.0",
|
"dotenv": "^16.0.0",
|
||||||
"express-session": "^1.17.2",
|
"express-session": "^1.17.2",
|
||||||
@ -44,6 +47,7 @@
|
|||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"pug": "^3.0.2",
|
"pug": "^3.0.2",
|
||||||
"qrcode": "^1.5.0",
|
"qrcode": "^1.5.0",
|
||||||
|
"redis": "^3.1.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rimraf": "^3.0.2",
|
"rimraf": "^3.0.2",
|
||||||
"rxjs": "^7.2.0",
|
"rxjs": "^7.2.0",
|
||||||
@ -58,6 +62,8 @@
|
|||||||
"@nestjs/schematics": "^8.0.0",
|
"@nestjs/schematics": "^8.0.0",
|
||||||
"@nestjs/testing": "^8.0.0",
|
"@nestjs/testing": "^8.0.0",
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
|
"@types/connect-redis": "^0.0.18",
|
||||||
|
"@types/csurf": "^1.11.2",
|
||||||
"@types/express": "^4.17.13",
|
"@types/express": "^4.17.13",
|
||||||
"@types/express-session": "^1.17.4",
|
"@types/express-session": "^1.17.4",
|
||||||
"@types/jest": "27.4.1",
|
"@types/jest": "27.4.1",
|
||||||
|
BIN
public/image/application.png
Normal file
BIN
public/image/application.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
@ -3,7 +3,7 @@
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background-color: #005b74;
|
background-color: var(--main-darker);
|
||||||
margin: 2rem -4rem;
|
margin: 2rem -4rem;
|
||||||
padding: 2rem 1rem;
|
padding: 2rem 1rem;
|
||||||
box-shadow: inset 0px 6px 62px -14px rgba(0, 0, 0, 0.45);
|
box-shadow: inset 0px 6px 62px -14px rgba(0, 0, 0, 0.45);
|
||||||
@ -22,7 +22,12 @@
|
|||||||
width: 120px;
|
width: 120px;
|
||||||
height: 120px;
|
height: 120px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
background-color: #b5b5b5;
|
background-color: var(--main-darker);
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&-content {
|
&-content {
|
||||||
|
@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
.center-box {
|
.center-box {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
background-color: #2e6b81;
|
background-color: var(--main);
|
||||||
color: #fff;
|
color: var(--text-color);
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
padding: 4rem;
|
padding: 4rem;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -22,8 +22,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-addon {
|
&-addon {
|
||||||
color: #fff;
|
color: var(--text-color);
|
||||||
background-color: #042b3a;
|
background-color: var(--main-dark);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
max-width: 600px;
|
max-width: 600px;
|
||||||
margin: 0 auto 2rem auto;
|
margin: 0 auto 2rem auto;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
border: none;
|
border: none;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
outline: 0px solid #00c0ff8a;
|
outline: 0px solid var(--focus-outline);
|
||||||
background-color: var(--btn-background);
|
background-color: var(--btn-background);
|
||||||
color: var(--btn-color);
|
color: var(--btn-color);
|
||||||
|
|
||||||
@ -14,18 +14,23 @@
|
|||||||
|
|
||||||
&-link {
|
&-link {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
color: #fff;
|
color: var(--text-color);
|
||||||
|
text-shadow: var(--text-shadow) 1px 1px 2px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
}
|
|
||||||
|
|
||||||
text-shadow: none;
|
text-decoration: underline;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: var(--btn-background-hover);
|
background-color: var(--btn-background-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 4px solid #00c0ff8a;
|
outline: 4px solid var(--focus-outline);
|
||||||
}
|
}
|
||||||
|
|
||||||
&-primary {
|
&-primary {
|
||||||
|
18
src/fe/scss/_colors.scss
Normal file
18
src/fe/scss/_colors.scss
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
:root {
|
||||||
|
--black: #000;
|
||||||
|
--white: #fff;
|
||||||
|
|
||||||
|
--text-color: var(--white);
|
||||||
|
--text-shadow: var(--black);
|
||||||
|
|
||||||
|
--form-border: #707070;
|
||||||
|
--form-border-hover: #5c5c5c;
|
||||||
|
|
||||||
|
--main-background: #314550;
|
||||||
|
--main: #2e6b81;
|
||||||
|
--main-light: #519eb9;
|
||||||
|
--main-darker: #005b74;
|
||||||
|
--main-dark: #042b3a;
|
||||||
|
|
||||||
|
--focus-outline: #00c0ff8a;
|
||||||
|
}
|
@ -19,16 +19,16 @@ input.form-control {
|
|||||||
padding: 8px;
|
padding: 8px;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
border: 1px solid #707070;
|
border: 1px solid var(--form-border);
|
||||||
|
|
||||||
transition: outline 0.15s linear;
|
transition: outline 0.15s linear;
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 4px solid #00c0ff8a;
|
outline: 4px solid var(--focus-outline);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover,
|
&:hover,
|
||||||
&:focus {
|
&:focus {
|
||||||
border: 1px solid #5c5c5c;
|
border: 1px solid var(--form-border-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
&__content {
|
&__content {
|
||||||
max-width: 800px;
|
max-width: 800px;
|
||||||
background-color: #2e6b81;
|
background-color: var(--main);
|
||||||
box-shadow: 0px 6px 62px -14px rgba(0, 0, 0, 0.45);
|
box-shadow: 0px 6px 62px -14px rgba(0, 0, 0, 0.45);
|
||||||
margin: 4rem auto 0 auto;
|
margin: 4rem auto 0 auto;
|
||||||
}
|
}
|
||||||
@ -36,7 +36,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
border-top: 1px solid #005b74;
|
border-top: 1px solid var(--main-darker);
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -46,7 +46,7 @@
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
border-bottom: 1px solid #005b74;
|
border-bottom: 1px solid var(--main-darker);
|
||||||
|
|
||||||
&-button .btn {
|
&-button .btn {
|
||||||
min-width: initial;
|
min-width: initial;
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
&__nav {
|
&__nav {
|
||||||
padding: 2rem 0rem;
|
padding: 2rem 0rem;
|
||||||
background-color: #005b74;
|
background-color: var(--main-darker);
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
display: flex;
|
display: flex;
|
||||||
@ -31,13 +31,13 @@
|
|||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.active,
|
&.active {
|
||||||
&:focus-visible {
|
border-right-color: var(--main-light);
|
||||||
border-right-color: #519eb9;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:focus {
|
&:focus {
|
||||||
outline: 4px solid #00c0ff8a;
|
outline: 4px solid var(--focus-outline);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -56,6 +56,7 @@
|
|||||||
height: 120px;
|
height: 120px;
|
||||||
width: 120px;
|
width: 120px;
|
||||||
flex: 0 0 120px;
|
flex: 0 0 120px;
|
||||||
|
background-color: var(--main-darker);
|
||||||
margin: auto;
|
margin: auto;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -78,7 +79,64 @@
|
|||||||
#crop-result {
|
#crop-result {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
max-height: 50vh;
|
max-height: 50vh;
|
||||||
background-color: #005b74;
|
background-color: var(--main-darker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.authorization {
|
||||||
|
&__client {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
min-height: 120px;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
|
||||||
|
&-image {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
background-color: var(--main-darker);
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-urls {
|
||||||
|
margin-top: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-wrap: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-url {
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-description {
|
||||||
|
margin-bottom: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include break-on(xs, down) {
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
@import 'breakpoint';
|
@import 'breakpoint';
|
||||||
|
@import 'colors';
|
||||||
@import 'block';
|
@import 'block';
|
||||||
@import 'form';
|
@import 'form';
|
||||||
@import 'button';
|
@import 'button';
|
||||||
@ -23,12 +24,12 @@ body {
|
|||||||
body {
|
body {
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||||
background-color: #314550;
|
background-color: var(--main-background);
|
||||||
text-shadow: black 1px 1px 2px;
|
text-shadow: var(--text-shadow) 1px 1px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
color: #fff;
|
color: var(--text-color);
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
@ -44,6 +44,10 @@ export class AvatarModal extends Modal {
|
|||||||
public initialize(): void {
|
public initialize(): void {
|
||||||
super.initialize();
|
super.initialize();
|
||||||
|
|
||||||
|
if (!this.modal) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.csrf = (document.querySelector('#csrf') as HTMLInputElement).value;
|
this.csrf = (document.querySelector('#csrf') as HTMLInputElement).value;
|
||||||
this.stages = this.modal?.querySelectorAll(
|
this.stages = this.modal?.querySelectorAll(
|
||||||
'[data-upload-step]',
|
'[data-upload-step]',
|
||||||
@ -131,7 +135,7 @@ export class AvatarModal extends Modal {
|
|||||||
this.uploadBtn.addEventListener('click', () => {
|
this.uploadBtn.addEventListener('click', () => {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('file', this.cropResultBlob);
|
formData.append('file', this.cropResultBlob);
|
||||||
formData.append('csrf', this.csrf);
|
formData.append('_csrf', this.csrf);
|
||||||
|
|
||||||
// TODO: error
|
// TODO: error
|
||||||
fetch('/account/avatar', {
|
fetch('/account/avatar', {
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
|
const isVisible = (elem: HTMLElement) =>
|
||||||
|
!!elem &&
|
||||||
|
!!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
|
||||||
|
|
||||||
export class Modal {
|
export class Modal {
|
||||||
public triggers?: NodeListOf<HTMLElement>;
|
public triggers?: NodeListOf<HTMLElement>;
|
||||||
public modal?: HTMLElement;
|
public modal?: HTMLElement;
|
||||||
|
public modalContent?: HTMLElement;
|
||||||
protected focusLock: HTMLElement[] = [];
|
protected focusLock: HTMLElement[] = [];
|
||||||
protected trigger?: HTMLElement;
|
protected trigger?: HTMLElement;
|
||||||
|
|
||||||
@ -13,6 +18,7 @@ export class Modal {
|
|||||||
|
|
||||||
this.modal.style.display = 'none';
|
this.modal.style.display = 'none';
|
||||||
this.removeFocusLock();
|
this.removeFocusLock();
|
||||||
|
this.removeClickListener();
|
||||||
}
|
}
|
||||||
|
|
||||||
public open(): void {
|
public open(): void {
|
||||||
@ -22,6 +28,10 @@ export class Modal {
|
|||||||
|
|
||||||
this.modal.style.display = 'block';
|
this.modal.style.display = 'block';
|
||||||
this.createFocusLock();
|
this.createFocusLock();
|
||||||
|
|
||||||
|
setTimeout(() =>
|
||||||
|
document.addEventListener('click', this.outsideClickListener),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public initialize(): void {
|
public initialize(): void {
|
||||||
@ -44,12 +54,28 @@ export class Modal {
|
|||||||
if (this.modal) {
|
if (this.modal) {
|
||||||
const attrLabel = `modal_${this.name}_label`;
|
const attrLabel = `modal_${this.name}_label`;
|
||||||
const label = this.modal.querySelector('.modal__title');
|
const label = this.modal.querySelector('.modal__title');
|
||||||
|
this.modalContent = this.modal.querySelector('.modal__content');
|
||||||
this.modal.setAttribute('aria-modal', 'true');
|
this.modal.setAttribute('aria-modal', 'true');
|
||||||
|
this.modal.setAttribute('role', 'dialog');
|
||||||
this.modal.setAttribute('aria-labelledby', attrLabel);
|
this.modal.setAttribute('aria-labelledby', attrLabel);
|
||||||
label.setAttribute('id', attrLabel);
|
label.setAttribute('id', attrLabel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private outsideClickListener = (event: Event) => {
|
||||||
|
if (
|
||||||
|
!this.modalContent.contains(event.target as HTMLElement) &&
|
||||||
|
isVisible(this.modalContent)
|
||||||
|
) {
|
||||||
|
this.reset();
|
||||||
|
this.removeClickListener();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private removeClickListener = () => {
|
||||||
|
document.removeEventListener('click', this.outsideClickListener);
|
||||||
|
};
|
||||||
|
|
||||||
private getFocusable(): HTMLElement[] {
|
private getFocusable(): HTMLElement[] {
|
||||||
const focusable = Array.from(
|
const focusable = Array.from(
|
||||||
this.modal.querySelectorAll(
|
this.modal.querySelectorAll(
|
||||||
|
@ -14,6 +14,12 @@ export class ModalManager {
|
|||||||
this.close();
|
this.close();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.addEventListener('keyup', (evt: KeyboardEvent) => {
|
||||||
|
if (evt.key === 'Escape') {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public register<T extends Modal>(item: T): T {
|
public register<T extends Modal>(item: T): T {
|
||||||
|
13
src/main.ts
13
src/main.ts
@ -2,6 +2,8 @@ import { NestFactory } from '@nestjs/core';
|
|||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import * as dotenv from 'dotenv';
|
import * as dotenv from 'dotenv';
|
||||||
import * as session from 'express-session';
|
import * as session from 'express-session';
|
||||||
|
import * as connectRedis from 'connect-redis';
|
||||||
|
import * as redis from 'redis';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||||
|
|
||||||
@ -9,11 +11,22 @@ dotenv.config();
|
|||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
const app = await NestFactory.create<NestExpressApplication>(AppModule);
|
||||||
|
|
||||||
|
const RedisStore = connectRedis(session);
|
||||||
|
const redisClient = redis.createClient({
|
||||||
|
host: 'localhost',
|
||||||
|
port: 6379,
|
||||||
|
});
|
||||||
|
|
||||||
|
// app.use(express.urlencoded());
|
||||||
|
// app.use(cookieParser());
|
||||||
|
|
||||||
app.use(
|
app.use(
|
||||||
session({
|
session({
|
||||||
secret: process.env.SESSION_SECRET,
|
secret: process.env.SESSION_SECRET,
|
||||||
resave: true,
|
resave: true,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
|
store: new RedisStore({ client: redisClient }),
|
||||||
cookie: {
|
cookie: {
|
||||||
sameSite: 'lax',
|
sameSite: 'lax',
|
||||||
secure: process.env.NODE_ENV === 'production',
|
secure: process.env.NODE_ENV === 'production',
|
||||||
|
@ -7,6 +7,7 @@ export class CSRFMiddleware implements NestMiddleware {
|
|||||||
constructor(private readonly tokenService: TokenService) {}
|
constructor(private readonly tokenService: TokenService) {}
|
||||||
|
|
||||||
use(req: Request, res: Response, next: NextFunction) {
|
use(req: Request, res: Response, next: NextFunction) {
|
||||||
|
// TODO: do not store in session, keep the amount of pointless sessions down
|
||||||
if (!req.session.csrf) {
|
if (!req.session.csrf) {
|
||||||
req.session.csrf = this.tokenService.generateString(64);
|
req.session.csrf = this.tokenService.generateString(64);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ export class ValidateCSRFMiddleware implements NestMiddleware {
|
|||||||
return next();
|
return next();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.body.csrf !== req.session.csrf) {
|
if (req.body._csrf !== req.session.csrf) {
|
||||||
return next(new Error('Invalid session'));
|
return next(new Error('Invalid session'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,11 +35,8 @@ export class LoginController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Render('login/login')
|
@Render('login/login')
|
||||||
public loginView(
|
public loginView(@Req() req: Request): Record<string, any> {
|
||||||
@Session() session: SessionData,
|
return this.formUtil.populateTemplate(req);
|
||||||
@Req() req: Request,
|
|
||||||
): Record<string, any> {
|
|
||||||
return this.formUtil.populateTemplate(req, session);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
@ -232,14 +229,14 @@ export class LoginController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
res.render('login/password', {
|
res.render('login/password', {
|
||||||
...this.formUtil.populateTemplate(req, session),
|
...this.formUtil.populateTemplate(req),
|
||||||
token: true,
|
token: true,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
res.render('login/password', {
|
res.render('login/password', {
|
||||||
...this.formUtil.populateTemplate(req, session),
|
...this.formUtil.populateTemplate(req),
|
||||||
token: false,
|
token: false,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,11 @@ export class UserAdapter implements OAuth2UserAdapter {
|
|||||||
clientId: string,
|
clientId: string,
|
||||||
scope: string | string[],
|
scope: string | string[],
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
return false;
|
return this._service.clientService.hasAuthorized(
|
||||||
|
userId,
|
||||||
|
clientId,
|
||||||
|
this._service.splitScope(scope),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async consent(
|
async consent(
|
||||||
@ -52,6 +56,13 @@ export class UserAdapter implements OAuth2UserAdapter {
|
|||||||
clientId: string,
|
clientId: string,
|
||||||
scope: string | string[],
|
scope: string | string[],
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
const client = await this._service.clientService.getById(clientId);
|
||||||
|
const user = await this._service.userService.getById(userId);
|
||||||
|
await this._service.clientService.createAuthorization(
|
||||||
|
user,
|
||||||
|
client,
|
||||||
|
this._service.splitScope(scope),
|
||||||
|
);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,11 +64,15 @@ export class OAuth2Service {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public splitScope(scope: string): string[] {
|
public splitScope(scope: string | string[]): string[] {
|
||||||
if (!scope) {
|
if (!scope) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(scope)) {
|
||||||
|
return scope;
|
||||||
|
}
|
||||||
|
|
||||||
return scope.includes(',')
|
return scope.includes(',')
|
||||||
? scope.split(',').map((item) => item.trim())
|
? scope.split(',').map((item) => item.trim())
|
||||||
: scope.split(' ');
|
: scope.split(' ');
|
||||||
|
@ -25,11 +25,8 @@ export class RegisterController {
|
|||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@Render('register')
|
@Render('register')
|
||||||
public registerView(
|
public registerView(@Req() req: Request): Record<string, any> {
|
||||||
@Session() session: SessionData,
|
return this.formUtil.populateTemplate(req);
|
||||||
@Req() req: Request,
|
|
||||||
): Record<string, any> {
|
|
||||||
return this.formUtil.populateTemplate(req, session);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@ -1,19 +1,25 @@
|
|||||||
import {
|
import {
|
||||||
BadRequestException,
|
BadRequestException,
|
||||||
|
Body,
|
||||||
Controller,
|
Controller,
|
||||||
|
Delete,
|
||||||
Get,
|
Get,
|
||||||
|
Param,
|
||||||
Post,
|
Post,
|
||||||
Redirect,
|
Redirect,
|
||||||
Render,
|
Render,
|
||||||
Req,
|
Req,
|
||||||
|
Res,
|
||||||
Session,
|
Session,
|
||||||
|
UnauthorizedException,
|
||||||
UploadedFile,
|
UploadedFile,
|
||||||
UseInterceptors,
|
UseInterceptors,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
import { Request } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { SessionData } from 'express-session';
|
import { SessionData } from 'express-session';
|
||||||
import { unlink } from 'fs/promises';
|
import { unlink } from 'fs/promises';
|
||||||
|
import { OAuth2ClientService } from 'src/modules/objects/oauth2-client/oauth2-client.service';
|
||||||
import { UploadService } from 'src/modules/objects/upload/upload.service';
|
import { UploadService } from 'src/modules/objects/upload/upload.service';
|
||||||
import { UserService } from 'src/modules/objects/user/user.service';
|
import { UserService } from 'src/modules/objects/user/user.service';
|
||||||
import { FormUtilityService } from 'src/modules/utility/services/form-utility.service';
|
import { FormUtilityService } from 'src/modules/utility/services/form-utility.service';
|
||||||
@ -26,6 +32,7 @@ export class SettingsController {
|
|||||||
private readonly _form: FormUtilityService,
|
private readonly _form: FormUtilityService,
|
||||||
private readonly _upload: UploadService,
|
private readonly _upload: UploadService,
|
||||||
private readonly _user: UserService,
|
private readonly _user: UserService,
|
||||||
|
private readonly _client: OAuth2ClientService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get()
|
@Get()
|
||||||
@ -36,8 +43,42 @@ export class SettingsController {
|
|||||||
|
|
||||||
@Get('general')
|
@Get('general')
|
||||||
@Render('settings/general')
|
@Render('settings/general')
|
||||||
public general(@Req() req: Request, @Session() sess: SessionData) {
|
public general(@Req() req: Request) {
|
||||||
return this._form.populateTemplate(req, sess, { user: req.user });
|
return this._form.populateTemplate(req, { user: req.user });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('general')
|
||||||
|
public async updateDisplayName(
|
||||||
|
@Req() req: Request,
|
||||||
|
@Res() res: Response,
|
||||||
|
@Body() body: { display_name?: string },
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const { display_name } = body;
|
||||||
|
if (!display_name) {
|
||||||
|
throw new Error('Display name is required.');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (display_name.length < 3 || display_name.length > 32) {
|
||||||
|
throw new Error(
|
||||||
|
'Display name must be between 3 and 32 characters long.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.user.display_name = display_name;
|
||||||
|
|
||||||
|
await this._user.updateUser(req.user);
|
||||||
|
req.flash('message', {
|
||||||
|
error: false,
|
||||||
|
text: 'Display name has been changed!',
|
||||||
|
});
|
||||||
|
} catch (e: any) {
|
||||||
|
req.flash('message', {
|
||||||
|
error: true,
|
||||||
|
text: e.message,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
res.redirect('/account/general');
|
||||||
}
|
}
|
||||||
|
|
||||||
@Post('avatar')
|
@Post('avatar')
|
||||||
@ -72,4 +113,55 @@ export class SettingsController {
|
|||||||
file: upload.file,
|
file: upload.file,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Post('avatar/delete')
|
||||||
|
public async deleteUserAvatar(@Req() req: Request, @Res() res: Response) {
|
||||||
|
this._user.deleteAvatar(req.user);
|
||||||
|
req.flash('message', {
|
||||||
|
error: false,
|
||||||
|
text: 'Avatar removed successfully.',
|
||||||
|
});
|
||||||
|
res.redirect('/account/general');
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('oauth2')
|
||||||
|
@Render('settings/oauth2')
|
||||||
|
public async authorizations(@Req() req: Request) {
|
||||||
|
const authorizations = await this._client.getAuthorizations(req.user);
|
||||||
|
return this._form.populateTemplate(req, { authorizations });
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('oauth2/revoke/:id')
|
||||||
|
public async revokeAuthorization(
|
||||||
|
@Req() req: Request,
|
||||||
|
@Res() res: Response,
|
||||||
|
@Param('id') id: number,
|
||||||
|
) {
|
||||||
|
const getAuth = await this._client.getAuthorization(req.user, id);
|
||||||
|
const jsreq =
|
||||||
|
req.header('content-type').startsWith('application/json') ||
|
||||||
|
req.header('accept').startsWith('application/json');
|
||||||
|
|
||||||
|
if (!getAuth) {
|
||||||
|
if (jsreq) {
|
||||||
|
throw new UnauthorizedException(
|
||||||
|
'Unauthorized or invalid revokation request',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
req.flash('message', {
|
||||||
|
error: true,
|
||||||
|
text: 'Unauthorized revokation.',
|
||||||
|
});
|
||||||
|
res.redirect('/account/oauth2');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await this._client.revokeAuthorization(getAuth);
|
||||||
|
|
||||||
|
if (jsreq) {
|
||||||
|
return res.json({ success: true });
|
||||||
|
}
|
||||||
|
res.redirect('/account/oauth2');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import { UserModule } from 'src/modules/objects/user/user.module';
|
|||||||
import { OAuth2Module } from '../oauth2/oauth2.module';
|
import { OAuth2Module } from '../oauth2/oauth2.module';
|
||||||
import { SettingsController } from './settings.controller';
|
import { SettingsController } from './settings.controller';
|
||||||
import { SettingsService } from './settings.service';
|
import { SettingsService } from './settings.service';
|
||||||
|
import { OAuth2ClientModule } from 'src/modules/objects/oauth2-client/oauth2-client.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
controllers: [SettingsController],
|
controllers: [SettingsController],
|
||||||
@ -26,6 +27,7 @@ import { SettingsService } from './settings.service';
|
|||||||
UploadModule,
|
UploadModule,
|
||||||
UserModule,
|
UserModule,
|
||||||
OAuth2Module,
|
OAuth2Module,
|
||||||
|
OAuth2ClientModule,
|
||||||
MulterModule.registerAsync({
|
MulterModule.registerAsync({
|
||||||
imports: [ConfigurationModule],
|
imports: [ConfigurationModule],
|
||||||
useFactory: async (config: ConfigurationService) => {
|
useFactory: async (config: ConfigurationService) => {
|
||||||
|
@ -42,7 +42,7 @@ export class TwoFactorController {
|
|||||||
const qrcode = await this.qr.createQRDataURI(url);
|
const qrcode = await this.qr.createQRDataURI(url);
|
||||||
|
|
||||||
res.render('two-factor/activate', {
|
res.render('two-factor/activate', {
|
||||||
...this.form.populateTemplate(req, session),
|
...this.form.populateTemplate(req),
|
||||||
qrcode,
|
qrcode,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { Repository } from 'typeorm';
|
import { DeleteResult, Repository } from 'typeorm';
|
||||||
|
import { User } from '../user/user.entity';
|
||||||
import { OAuth2ClientAuthorization } from './oauth2-client-authorization.entity';
|
import { OAuth2ClientAuthorization } from './oauth2-client-authorization.entity';
|
||||||
import {
|
import {
|
||||||
OAuth2ClientURL,
|
OAuth2ClientURL,
|
||||||
@ -18,6 +19,98 @@ export class OAuth2ClientService {
|
|||||||
private clientAuthRepository: Repository<OAuth2ClientAuthorization>,
|
private clientAuthRepository: Repository<OAuth2ClientAuthorization>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
public async hasAuthorized(
|
||||||
|
userId: number,
|
||||||
|
clientId: string,
|
||||||
|
scope: string[],
|
||||||
|
): Promise<boolean> {
|
||||||
|
const authorization = await this.clientAuthRepository.findOne({
|
||||||
|
where: {
|
||||||
|
user: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
client: {
|
||||||
|
client_id: clientId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
relations: ['user', 'client'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!authorization) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scopes must have been allowed
|
||||||
|
const splitScope = authorization.scope.split(' ');
|
||||||
|
if (scope.every((item) => splitScope.includes(item))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async createAuthorization(
|
||||||
|
user: User,
|
||||||
|
client: OAuth2Client,
|
||||||
|
scope: string[],
|
||||||
|
): Promise<OAuth2ClientAuthorization> {
|
||||||
|
const existing = await this.clientAuthRepository.findOne({
|
||||||
|
where: {
|
||||||
|
user,
|
||||||
|
client,
|
||||||
|
},
|
||||||
|
relations: ['user', 'client'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
const splitScope = existing.scope.split(' ');
|
||||||
|
scope.forEach((item) => {
|
||||||
|
if (!splitScope.includes(item)) {
|
||||||
|
splitScope.push(item);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
existing.scope = splitScope.join(' ');
|
||||||
|
await this.clientAuthRepository.save(existing);
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authorization = new OAuth2ClientAuthorization();
|
||||||
|
authorization.user = user;
|
||||||
|
authorization.client = client;
|
||||||
|
authorization.scope = scope.join(' ');
|
||||||
|
await this.clientAuthRepository.insert(authorization);
|
||||||
|
return authorization;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAuthorizations(
|
||||||
|
user: User,
|
||||||
|
): Promise<OAuth2ClientAuthorization[]> {
|
||||||
|
return this.clientAuthRepository.find({
|
||||||
|
relations: ['user', 'client', 'client.urls'],
|
||||||
|
where: { user },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async revokeAuthorization(
|
||||||
|
auth: OAuth2ClientAuthorization,
|
||||||
|
): Promise<OAuth2ClientAuthorization> {
|
||||||
|
console.log(auth);
|
||||||
|
return this.clientAuthRepository.remove(auth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getAuthorization(
|
||||||
|
user: User,
|
||||||
|
authId: number,
|
||||||
|
): Promise<OAuth2ClientAuthorization> {
|
||||||
|
return this.clientAuthRepository.findOne({
|
||||||
|
where: {
|
||||||
|
user,
|
||||||
|
id: authId,
|
||||||
|
},
|
||||||
|
relations: ['user'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public async getById(id: string | number): Promise<OAuth2Client> {
|
public async getById(id: string | number): Promise<OAuth2Client> {
|
||||||
let client: OAuth2Client;
|
let client: OAuth2Client;
|
||||||
|
|
||||||
|
@ -77,6 +77,12 @@ export class UserService {
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async deleteAvatar(user: User): Promise<void> {
|
||||||
|
if (user.picture) {
|
||||||
|
await this.upload.delete(user.picture);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async comparePasswords(
|
public async comparePasswords(
|
||||||
hash: string,
|
hash: string,
|
||||||
password: string,
|
password: string,
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { Request } from 'express';
|
import { Request } from 'express';
|
||||||
import { SessionData } from 'express-session';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class FormUtilityService {
|
export class FormUtilityService {
|
||||||
@ -25,7 +24,6 @@ export class FormUtilityService {
|
|||||||
*/
|
*/
|
||||||
public populateTemplate(
|
public populateTemplate(
|
||||||
req: Request,
|
req: Request,
|
||||||
session: SessionData,
|
|
||||||
additional: Record<string, any> = {},
|
additional: Record<string, any> = {},
|
||||||
): Record<string, any> {
|
): Record<string, any> {
|
||||||
const message = req.flash('message')[0] || {};
|
const message = req.flash('message')[0] || {};
|
||||||
@ -35,7 +33,7 @@ export class FormUtilityService {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
path: req.originalUrl,
|
path: req.originalUrl,
|
||||||
csrf: session.csrf,
|
csrf: req.session.csrf,
|
||||||
message,
|
message,
|
||||||
form,
|
form,
|
||||||
...additional,
|
...additional,
|
||||||
|
@ -12,12 +12,20 @@ block body
|
|||||||
.authorize
|
.authorize
|
||||||
.authorize__user
|
.authorize__user
|
||||||
.authorize__user-image
|
.authorize__user-image
|
||||||
|
if user.picture
|
||||||
|
img(src='/uploads/' + user.picture.file, alt=user.username)
|
||||||
|
else
|
||||||
|
img(src='/public/image/avatar.png', alt='No picture')
|
||||||
.authorize__user-content
|
.authorize__user-content
|
||||||
span.authorize__user-title #{user.display_name}
|
span.authorize__user-title #{user.display_name}
|
||||||
span.authorize__user-user @#{user.username}
|
span.authorize__user-user @#{user.username}
|
||||||
.authorize__center
|
.authorize__center
|
||||||
.authorize__client
|
.authorize__client
|
||||||
.authorize__client-image
|
.authorize__client-image
|
||||||
|
if client.picture
|
||||||
|
img(src='/uploads/' + client.picture.file, alt=client.title)
|
||||||
|
else
|
||||||
|
img(src='/public/image/application.png', alt='No picture')
|
||||||
.authorize__client-content
|
.authorize__client-content
|
||||||
span.authorize__client-title #{client.title}
|
span.authorize__client-title #{client.title}
|
||||||
span.authorize__client-description #{client.description}
|
span.authorize__client-description #{client.description}
|
||||||
@ -45,12 +53,12 @@ block body
|
|||||||
|
|
||||||
form(method="POST", action="")
|
form(method="POST", action="")
|
||||||
div.form-container
|
div.form-container
|
||||||
input(type="hidden", name="csrf", value=csrf)
|
input(type="hidden", name="_csrf", value=csrf)
|
||||||
input(type="hidden", name="decision", value="1")
|
input(type="hidden", name="decision", value="1")
|
||||||
button.btn.btn-primary(type="submit") Authorize
|
button.btn.btn-primary(type="submit") Authorize
|
||||||
|
|
||||||
form(method="POST", action="")
|
form(method="POST", action="")
|
||||||
div.form-container
|
div.form-container
|
||||||
input(type="hidden", name="csrf", value=csrf)
|
input(type="hidden", name="_csrf", value=csrf)
|
||||||
input(type="hidden", name="decision", value="0")
|
input(type="hidden", name="decision", value="0")
|
||||||
button.btn.btn-link(type="submit") Reject
|
button.btn.btn-link(type="submit") Reject
|
||||||
|
@ -18,7 +18,7 @@ block body
|
|||||||
|
|
||||||
form(method="post")
|
form(method="post")
|
||||||
div.form-container
|
div.form-container
|
||||||
input#csrf(type="hidden", name="csrf", value=csrf)
|
input#csrf(type="hidden", name="_csrf", value=csrf)
|
||||||
label.form-label(for="username") Username
|
label.form-label(for="username") Username
|
||||||
input.form-control#username(type="text", name="username", placeholder="Username", autofocus, value=form.username)
|
input.form-control#username(type="text", name="username", placeholder="Username", autofocus, value=form.username)
|
||||||
label.form-label(for="password") Password
|
label.form-label(for="password") Password
|
||||||
|
@ -19,7 +19,7 @@ block body
|
|||||||
|
|
||||||
form(method="post")
|
form(method="post")
|
||||||
div.form-container
|
div.form-container
|
||||||
input#csrf(type="hidden", name="csrf", value=csrf)
|
input#csrf(type="hidden", name="_csrf", value=csrf)
|
||||||
label.form-label(for="password") New password
|
label.form-label(for="password") New password
|
||||||
input.form-control#password(type="password", name="password", autofocus, placeholder="Password")
|
input.form-control#password(type="password", name="password", autofocus, placeholder="Password")
|
||||||
small.form-hint Must be at least 8 characters long, contain a capital and lowercase letter and a number.
|
small.form-hint Must be at least 8 characters long, contain a capital and lowercase letter and a number.
|
||||||
@ -31,7 +31,7 @@ block body
|
|||||||
p If you have forgotten your password, please enter your accounts email address and we will send you a link to recover it.
|
p If you have forgotten your password, please enter your accounts email address and we will send you a link to recover it.
|
||||||
form(method="post")
|
form(method="post")
|
||||||
div.form-container
|
div.form-container
|
||||||
input#csrf(type="hidden", name="csrf", value=csrf)
|
input#csrf(type="hidden", name="_csrf", value=csrf)
|
||||||
label.form-label(for="email") Email address
|
label.form-label(for="email") Email address
|
||||||
input.form-control#email(type="email", name="email", autofocus, placeholder="Email addres")
|
input.form-control#email(type="email", name="email", autofocus, placeholder="Email addres")
|
||||||
button.btn.btn-primary(type="submit") Send recovery email
|
button.btn.btn-primary(type="submit") Send recovery email
|
||||||
|
@ -18,7 +18,7 @@ block body
|
|||||||
|
|
||||||
form(method="post")
|
form(method="post")
|
||||||
div.form-container
|
div.form-container
|
||||||
input#csrf(type="hidden", name="csrf", value=csrf)
|
input#csrf(type="hidden", name="_csrf", value=csrf)
|
||||||
label.form-label(for="totp") Code
|
label.form-label(for="totp") Code
|
||||||
input.form-control#totp(type="text", name="totp", autofocus, placeholder="xxxxxx")
|
input.form-control#totp(type="text", name="totp", autofocus, placeholder="xxxxxx")
|
||||||
button.btn.btn-primary(type="submit") Log in
|
button.btn.btn-primary(type="submit") Log in
|
||||||
|
@ -18,7 +18,7 @@ block body
|
|||||||
|
|
||||||
form(method="post")
|
form(method="post")
|
||||||
div.form-container
|
div.form-container
|
||||||
input#csrf(type="hidden", name="csrf", value=csrf)
|
input#csrf(type="hidden", name="_csrf", value=csrf)
|
||||||
|
|
||||||
label.form-label(for="username") Username
|
label.form-label(for="username") Username
|
||||||
input.form-control#username(type="text", name="username", placeholder="Username", autofocus, value=form.username)
|
input.form-control#username(type="text", name="username", placeholder="Username", autofocus, value=form.username)
|
||||||
|
@ -16,7 +16,7 @@ block settings
|
|||||||
.col
|
.col
|
||||||
form(method="post")
|
form(method="post")
|
||||||
div.form-container
|
div.form-container
|
||||||
input#csrf(type="hidden", name="csrf", value=csrf)
|
input#csrf(type="hidden", name="_csrf", value=csrf)
|
||||||
label.form-label(for="username") Username
|
label.form-label(for="username") Username
|
||||||
input.form-control#username(type="text", name="username", placeholder="Username", disabled, value=user.username)
|
input.form-control#username(type="text", name="username", placeholder="Username", disabled, value=user.username)
|
||||||
label.form-label(for="display_name") Display Name
|
label.form-label(for="display_name") Display Name
|
||||||
@ -33,16 +33,18 @@ block settings
|
|||||||
.flex-column(data-script="flex")
|
.flex-column(data-script="flex")
|
||||||
button.btn.btn-primary(data-modal-trigger="avatar") Change avatar
|
button.btn.btn-primary(data-modal-trigger="avatar") Change avatar
|
||||||
if user.picture
|
if user.picture
|
||||||
button.btn.btn-link#remove-avatar Remove avatar
|
form(method="post", action="/account/avatar/delete")
|
||||||
|
input(type="hidden", name="_csrf", value=csrf)
|
||||||
|
button.btn.btn-link#remove-avatar(type="submit") Remove avatar
|
||||||
|
|
||||||
form(method="post", data-noscript, action="/account/avatar", enctype="multipart/form-data")
|
form(method="post", data-noscript, action="/account/avatar", enctype="multipart/form-data")
|
||||||
div.form-container
|
div.form-container
|
||||||
input#csrf(type="hidden", name="csrf", value=csrf)
|
input(type="hidden", name="_csrf", value=csrf)
|
||||||
label.form-label(for="image") Image
|
label.form-label(for="image") Image
|
||||||
input.form-control#image(type="file", name="file")
|
input.form-control#image(type="file", name="file")
|
||||||
small.form-hint Must be less than 10 MB and 1:1 aspect ratio. Enable JavaScript to custom crop your image.
|
small.form-hint Must be less than 10 MB and 1:1 aspect ratio. Enable JavaScript to custom crop your image.
|
||||||
button.btn.btn-primary(type="submit") Change
|
button.btn.btn-primary(type="submit") Change
|
||||||
.modal#avatar-modal(data-modal="avatar", aria-live="polite", role="modal", style="display: none")
|
.modal#avatar-modal(data-modal="avatar", style="display: none")
|
||||||
.modal__content
|
.modal__content
|
||||||
.modal__title
|
.modal__title
|
||||||
|Change avatar
|
|Change avatar
|
||||||
|
@ -7,10 +7,10 @@ block body
|
|||||||
nav.sidebar.settings__nav
|
nav.sidebar.settings__nav
|
||||||
ul
|
ul
|
||||||
li
|
li
|
||||||
a(href="/account/general") General
|
a(href="/account/general", class=path === '/account/general' ? 'active' : '') General
|
||||||
li
|
li
|
||||||
a(href="/account/oauth2") Authorizations
|
a(href="/account/oauth2", class=path === '/account/oauth2' ? 'active' : '') Authorizations
|
||||||
li
|
li
|
||||||
a(href="/account/security") Security
|
a(href="/account/security", class=path === '/account/security' ? 'active' : '') Security
|
||||||
section.content.settings__content
|
section.content.settings__content
|
||||||
block settings
|
block settings
|
||||||
|
44
views/settings/oauth2.pug
Normal file
44
views/settings/oauth2.pug
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
extends ./layout.pug
|
||||||
|
|
||||||
|
block title
|
||||||
|
|Authorizations - Account settings | Icy Network
|
||||||
|
|
||||||
|
block settings
|
||||||
|
h1 Authorizations
|
||||||
|
if message.text
|
||||||
|
if message.error
|
||||||
|
.alert.alert-danger
|
||||||
|
span #{message.text}
|
||||||
|
else
|
||||||
|
.alert.alert-success
|
||||||
|
span #{message.text}
|
||||||
|
p These applications are authorized automatically when requested, provided you have already consented to the information they require.
|
||||||
|
p By revoking the authorization, you may be prompted to authorize the application again in the future. This does NOT ensure that your information
|
||||||
|
|is deleted by any third-party applications in question. Please contact each application's owner individually if you wish to remove your information
|
||||||
|
|from their servers.
|
||||||
|
hr
|
||||||
|
.authorization-wrapper
|
||||||
|
each auth in authorizations
|
||||||
|
.authorization__client
|
||||||
|
.authorization__client-image
|
||||||
|
if auth.client.picture
|
||||||
|
img(src='/uploads/' + auth.client.picture.file, alt=auth.client.title)
|
||||||
|
else
|
||||||
|
img(src='/public/image/application.png', alt='No picture')
|
||||||
|
.authorization__client-content
|
||||||
|
span.authorization__client-title #{auth.client.title}
|
||||||
|
span.authorization__client-description #{auth.client.description}
|
||||||
|
.authorization__client-urls
|
||||||
|
each url in auth.client.urls
|
||||||
|
if url.type == 'website'
|
||||||
|
a.authorization__client-url(href=url.url, target="_blank", rel="nofollow") Visit website
|
||||||
|
if url.type == 'privacy'
|
||||||
|
a.authorization__client-url(href=url.url, target="_blank", rel="nofollow") Privacy Policy
|
||||||
|
if url.type == 'terms'
|
||||||
|
a.authorization__client-url(href=url.url, target="_blank", rel="nofollow") Terms of Service
|
||||||
|
.authorization__client-actions
|
||||||
|
form(method="post",action="/account/oauth2/revoke/" + auth.id)
|
||||||
|
input(type="hidden", name="_csrf", value=csrf)
|
||||||
|
button.btn.btn-primary(type="submit") Revoke
|
||||||
|
if !authorizations.length
|
||||||
|
p.text-center You have not authorized any applications.
|
@ -21,7 +21,7 @@ block body
|
|||||||
|
|
||||||
form(method="post",autocomplete="off")
|
form(method="post",autocomplete="off")
|
||||||
div.form-container
|
div.form-container
|
||||||
input#csrf(type="hidden", name="csrf", value=csrf)
|
input#csrf(type="hidden", name="_csrf", value=csrf)
|
||||||
label.form-label(for="code") Code from authenticator app
|
label.form-label(for="code") Code from authenticator app
|
||||||
input.form-control#code(type="text", name="code", autofocus, placeholder="xxxxxx")
|
input.form-control#code(type="text", name="code", autofocus, placeholder="xxxxxx")
|
||||||
button.btn.btn-primary(type="submit") Activate
|
button.btn.btn-primary(type="submit") Activate
|
||||||
|
Reference in New Issue
Block a user