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/throttler": "^2.0.1",
|
||||
"bcrypt": "^5.0.1",
|
||||
"body-parser": "^1.19.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"connect-redis": "^6.1.3",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cropperjs": "^1.5.12",
|
||||
"dotenv": "^16.0.0",
|
||||
"express-session": "^1.17.2",
|
||||
@ -30,6 +33,7 @@
|
||||
"otplib": "^12.0.1",
|
||||
"pug": "^3.0.2",
|
||||
"qrcode": "^1.5.0",
|
||||
"redis": "^3.1.2",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0",
|
||||
@ -44,6 +48,8 @@
|
||||
"@nestjs/schematics": "^8.0.0",
|
||||
"@nestjs/testing": "^8.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-session": "^1.17.4",
|
||||
"@types/jest": "27.4.1",
|
||||
@ -3129,12 +3135,33 @@
|
||||
"@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": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
||||
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
||||
"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": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||
@ -3202,6 +3229,15 @@
|
||||
"@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": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
|
||||
@ -3326,6 +3362,15 @@
|
||||
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
||||
"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": {
|
||||
"version": "1.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz",
|
||||
@ -4986,6 +5031,14 @@
|
||||
"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": {
|
||||
"version": "2.15.3",
|
||||
"resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz",
|
||||
@ -5047,6 +5100,26 @@
|
||||
"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": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
@ -9982,6 +10055,56 @@
|
||||
"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": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||
@ -14606,12 +14729,33 @@
|
||||
"@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": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.2.tgz",
|
||||
"integrity": "sha512-t73xJJrvdTjXrn4jLS9VSGRbz0nUY3cl2DMGDU48lKl+HR9dbbjW2A9r3g40VA++mQpy6uuHg33gy7du2BKpog==",
|
||||
"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": {
|
||||
"version": "8.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.1.tgz",
|
||||
@ -14679,6 +14823,15 @@
|
||||
"@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": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
|
||||
@ -14803,6 +14956,15 @@
|
||||
"integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==",
|
||||
"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": {
|
||||
"version": "1.13.10",
|
||||
"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": {
|
||||
"version": "2.15.3",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
@ -19903,6 +20086,42 @@
|
||||
"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": {
|
||||
"version": "0.1.13",
|
||||
"resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.13.tgz",
|
||||
|
@ -30,8 +30,11 @@
|
||||
"@nestjs/serve-static": "^2.2.2",
|
||||
"@nestjs/throttler": "^2.0.1",
|
||||
"bcrypt": "^5.0.1",
|
||||
"body-parser": "^1.19.2",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.13.2",
|
||||
"connect-redis": "^6.1.3",
|
||||
"cookie-parser": "^1.4.6",
|
||||
"cropperjs": "^1.5.12",
|
||||
"dotenv": "^16.0.0",
|
||||
"express-session": "^1.17.2",
|
||||
@ -44,6 +47,7 @@
|
||||
"otplib": "^12.0.1",
|
||||
"pug": "^3.0.2",
|
||||
"qrcode": "^1.5.0",
|
||||
"redis": "^3.1.2",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.2.0",
|
||||
@ -58,6 +62,8 @@
|
||||
"@nestjs/schematics": "^8.0.0",
|
||||
"@nestjs/testing": "^8.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-session": "^1.17.4",
|
||||
"@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;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
background-color: #005b74;
|
||||
background-color: var(--main-darker);
|
||||
margin: 2rem -4rem;
|
||||
padding: 2rem 1rem;
|
||||
box-shadow: inset 0px 6px 62px -14px rgba(0, 0, 0, 0.45);
|
||||
@ -22,7 +22,12 @@
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
flex-shrink: 0;
|
||||
background-color: #b5b5b5;
|
||||
background-color: var(--main-darker);
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-content {
|
||||
|
@ -8,8 +8,8 @@
|
||||
|
||||
.center-box {
|
||||
max-width: 800px;
|
||||
background-color: #2e6b81;
|
||||
color: #fff;
|
||||
background-color: var(--main);
|
||||
color: var(--text-color);
|
||||
margin: 2rem auto;
|
||||
padding: 4rem;
|
||||
position: relative;
|
||||
@ -22,8 +22,8 @@
|
||||
}
|
||||
|
||||
&-addon {
|
||||
color: #fff;
|
||||
background-color: #042b3a;
|
||||
color: var(--text-color);
|
||||
background-color: var(--main-dark);
|
||||
overflow: hidden;
|
||||
max-width: 600px;
|
||||
margin: 0 auto 2rem auto;
|
||||
|
@ -5,7 +5,7 @@
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
outline: 0px solid #00c0ff8a;
|
||||
outline: 0px solid var(--focus-outline);
|
||||
background-color: var(--btn-background);
|
||||
color: var(--btn-color);
|
||||
|
||||
@ -14,18 +14,23 @@
|
||||
|
||||
&-link {
|
||||
font-size: 1rem;
|
||||
color: #fff;
|
||||
color: var(--text-color);
|
||||
text-shadow: var(--text-shadow) 1px 1px 2px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
text-shadow: none;
|
||||
text-decoration: underline;
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--btn-background-hover);
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 4px solid #00c0ff8a;
|
||||
outline: 4px solid var(--focus-outline);
|
||||
}
|
||||
|
||||
&-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;
|
||||
font-size: 1rem;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #707070;
|
||||
border: 1px solid var(--form-border);
|
||||
|
||||
transition: outline 0.15s linear;
|
||||
|
||||
&:focus {
|
||||
outline: 4px solid #00c0ff8a;
|
||||
outline: 4px solid var(--focus-outline);
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border: 1px solid #5c5c5c;
|
||||
border: 1px solid var(--form-border-hover);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
&__content {
|
||||
max-width: 800px;
|
||||
background-color: #2e6b81;
|
||||
background-color: var(--main);
|
||||
box-shadow: 0px 6px 62px -14px rgba(0, 0, 0, 0.45);
|
||||
margin: 4rem auto 0 auto;
|
||||
}
|
||||
@ -36,7 +36,7 @@
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
border-top: 1px solid #005b74;
|
||||
border-top: 1px solid var(--main-darker);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@ -46,7 +46,7 @@
|
||||
padding: 1rem;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
border-bottom: 1px solid #005b74;
|
||||
border-bottom: 1px solid var(--main-darker);
|
||||
|
||||
&-button .btn {
|
||||
min-width: initial;
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
&__nav {
|
||||
padding: 2rem 0rem;
|
||||
background-color: #005b74;
|
||||
background-color: var(--main-darker);
|
||||
|
||||
ul {
|
||||
display: flex;
|
||||
@ -31,13 +31,13 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
&.active,
|
||||
&:focus-visible {
|
||||
border-right-color: #519eb9;
|
||||
&.active {
|
||||
border-right-color: var(--main-light);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
outline: 4px solid #00c0ff8a;
|
||||
outline: 4px solid var(--focus-outline);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -56,6 +56,7 @@
|
||||
height: 120px;
|
||||
width: 120px;
|
||||
flex: 0 0 120px;
|
||||
background-color: var(--main-darker);
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
@ -78,7 +79,64 @@
|
||||
#crop-result {
|
||||
max-width: 100%;
|
||||
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 'colors';
|
||||
@import 'block';
|
||||
@import 'form';
|
||||
@import 'button';
|
||||
@ -23,12 +24,12 @@ body {
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen,
|
||||
Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
|
||||
background-color: #314550;
|
||||
text-shadow: black 1px 1px 2px;
|
||||
background-color: var(--main-background);
|
||||
text-shadow: var(--text-shadow) 1px 1px 2px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #fff;
|
||||
color: var(--text-color);
|
||||
|
||||
&:hover {
|
||||
text-decoration: none;
|
||||
|
@ -44,6 +44,10 @@ export class AvatarModal extends Modal {
|
||||
public initialize(): void {
|
||||
super.initialize();
|
||||
|
||||
if (!this.modal) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.csrf = (document.querySelector('#csrf') as HTMLInputElement).value;
|
||||
this.stages = this.modal?.querySelectorAll(
|
||||
'[data-upload-step]',
|
||||
@ -131,7 +135,7 @@ export class AvatarModal extends Modal {
|
||||
this.uploadBtn.addEventListener('click', () => {
|
||||
const formData = new FormData();
|
||||
formData.append('file', this.cropResultBlob);
|
||||
formData.append('csrf', this.csrf);
|
||||
formData.append('_csrf', this.csrf);
|
||||
|
||||
// TODO: error
|
||||
fetch('/account/avatar', {
|
||||
|
@ -1,6 +1,11 @@
|
||||
const isVisible = (elem: HTMLElement) =>
|
||||
!!elem &&
|
||||
!!(elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length);
|
||||
|
||||
export class Modal {
|
||||
public triggers?: NodeListOf<HTMLElement>;
|
||||
public modal?: HTMLElement;
|
||||
public modalContent?: HTMLElement;
|
||||
protected focusLock: HTMLElement[] = [];
|
||||
protected trigger?: HTMLElement;
|
||||
|
||||
@ -13,6 +18,7 @@ export class Modal {
|
||||
|
||||
this.modal.style.display = 'none';
|
||||
this.removeFocusLock();
|
||||
this.removeClickListener();
|
||||
}
|
||||
|
||||
public open(): void {
|
||||
@ -22,6 +28,10 @@ export class Modal {
|
||||
|
||||
this.modal.style.display = 'block';
|
||||
this.createFocusLock();
|
||||
|
||||
setTimeout(() =>
|
||||
document.addEventListener('click', this.outsideClickListener),
|
||||
);
|
||||
}
|
||||
|
||||
public initialize(): void {
|
||||
@ -44,12 +54,28 @@ export class Modal {
|
||||
if (this.modal) {
|
||||
const attrLabel = `modal_${this.name}_label`;
|
||||
const label = this.modal.querySelector('.modal__title');
|
||||
this.modalContent = this.modal.querySelector('.modal__content');
|
||||
this.modal.setAttribute('aria-modal', 'true');
|
||||
this.modal.setAttribute('role', 'dialog');
|
||||
this.modal.setAttribute('aria-labelledby', 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[] {
|
||||
const focusable = Array.from(
|
||||
this.modal.querySelectorAll(
|
||||
|
@ -14,6 +14,12 @@ export class ModalManager {
|
||||
this.close();
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('keyup', (evt: KeyboardEvent) => {
|
||||
if (evt.key === 'Escape') {
|
||||
this.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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 * as dotenv from 'dotenv';
|
||||
import * as session from 'express-session';
|
||||
import * as connectRedis from 'connect-redis';
|
||||
import * as redis from 'redis';
|
||||
import { join } from 'path';
|
||||
import { NestExpressApplication } from '@nestjs/platform-express';
|
||||
|
||||
@ -9,11 +11,22 @@ dotenv.config();
|
||||
|
||||
async function bootstrap() {
|
||||
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(
|
||||
session({
|
||||
secret: process.env.SESSION_SECRET,
|
||||
resave: true,
|
||||
saveUninitialized: false,
|
||||
store: new RedisStore({ client: redisClient }),
|
||||
cookie: {
|
||||
sameSite: 'lax',
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
|
@ -7,6 +7,7 @@ export class CSRFMiddleware implements NestMiddleware {
|
||||
constructor(private readonly tokenService: TokenService) {}
|
||||
|
||||
use(req: Request, res: Response, next: NextFunction) {
|
||||
// TODO: do not store in session, keep the amount of pointless sessions down
|
||||
if (!req.session.csrf) {
|
||||
req.session.csrf = this.tokenService.generateString(64);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ export class ValidateCSRFMiddleware implements NestMiddleware {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.body.csrf !== req.session.csrf) {
|
||||
if (req.body._csrf !== req.session.csrf) {
|
||||
return next(new Error('Invalid session'));
|
||||
}
|
||||
|
||||
|
@ -35,11 +35,8 @@ export class LoginController {
|
||||
|
||||
@Get()
|
||||
@Render('login/login')
|
||||
public loginView(
|
||||
@Session() session: SessionData,
|
||||
@Req() req: Request,
|
||||
): Record<string, any> {
|
||||
return this.formUtil.populateTemplate(req, session);
|
||||
public loginView(@Req() req: Request): Record<string, any> {
|
||||
return this.formUtil.populateTemplate(req);
|
||||
}
|
||||
|
||||
@Post()
|
||||
@ -232,14 +229,14 @@ export class LoginController {
|
||||
}
|
||||
|
||||
res.render('login/password', {
|
||||
...this.formUtil.populateTemplate(req, session),
|
||||
...this.formUtil.populateTemplate(req),
|
||||
token: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.render('login/password', {
|
||||
...this.formUtil.populateTemplate(req, session),
|
||||
...this.formUtil.populateTemplate(req),
|
||||
token: false,
|
||||
});
|
||||
}
|
||||
|
@ -44,7 +44,11 @@ export class UserAdapter implements OAuth2UserAdapter {
|
||||
clientId: string,
|
||||
scope: string | string[],
|
||||
): Promise<boolean> {
|
||||
return false;
|
||||
return this._service.clientService.hasAuthorized(
|
||||
userId,
|
||||
clientId,
|
||||
this._service.splitScope(scope),
|
||||
);
|
||||
}
|
||||
|
||||
async consent(
|
||||
@ -52,6 +56,13 @@ export class UserAdapter implements OAuth2UserAdapter {
|
||||
clientId: string,
|
||||
scope: string | string[],
|
||||
): 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;
|
||||
}
|
||||
}
|
||||
|
@ -64,11 +64,15 @@ export class OAuth2Service {
|
||||
}
|
||||
}
|
||||
|
||||
public splitScope(scope: string): string[] {
|
||||
public splitScope(scope: string | string[]): string[] {
|
||||
if (!scope) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (Array.isArray(scope)) {
|
||||
return scope;
|
||||
}
|
||||
|
||||
return scope.includes(',')
|
||||
? scope.split(',').map((item) => item.trim())
|
||||
: scope.split(' ');
|
||||
|
@ -25,11 +25,8 @@ export class RegisterController {
|
||||
|
||||
@Get()
|
||||
@Render('register')
|
||||
public registerView(
|
||||
@Session() session: SessionData,
|
||||
@Req() req: Request,
|
||||
): Record<string, any> {
|
||||
return this.formUtil.populateTemplate(req, session);
|
||||
public registerView(@Req() req: Request): Record<string, any> {
|
||||
return this.formUtil.populateTemplate(req);
|
||||
}
|
||||
|
||||
@Post()
|
||||
|
@ -1,19 +1,25 @@
|
||||
import {
|
||||
BadRequestException,
|
||||
Body,
|
||||
Controller,
|
||||
Delete,
|
||||
Get,
|
||||
Param,
|
||||
Post,
|
||||
Redirect,
|
||||
Render,
|
||||
Req,
|
||||
Res,
|
||||
Session,
|
||||
UnauthorizedException,
|
||||
UploadedFile,
|
||||
UseInterceptors,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { Request } from 'express';
|
||||
import { Request, Response } from 'express';
|
||||
import { SessionData } from 'express-session';
|
||||
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 { UserService } from 'src/modules/objects/user/user.service';
|
||||
import { FormUtilityService } from 'src/modules/utility/services/form-utility.service';
|
||||
@ -26,6 +32,7 @@ export class SettingsController {
|
||||
private readonly _form: FormUtilityService,
|
||||
private readonly _upload: UploadService,
|
||||
private readonly _user: UserService,
|
||||
private readonly _client: OAuth2ClientService,
|
||||
) {}
|
||||
|
||||
@Get()
|
||||
@ -36,8 +43,42 @@ export class SettingsController {
|
||||
|
||||
@Get('general')
|
||||
@Render('settings/general')
|
||||
public general(@Req() req: Request, @Session() sess: SessionData) {
|
||||
return this._form.populateTemplate(req, sess, { user: req.user });
|
||||
public general(@Req() req: Request) {
|
||||
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')
|
||||
@ -72,4 +113,55 @@ export class SettingsController {
|
||||
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 { SettingsController } from './settings.controller';
|
||||
import { SettingsService } from './settings.service';
|
||||
import { OAuth2ClientModule } from 'src/modules/objects/oauth2-client/oauth2-client.module';
|
||||
|
||||
@Module({
|
||||
controllers: [SettingsController],
|
||||
@ -26,6 +27,7 @@ import { SettingsService } from './settings.service';
|
||||
UploadModule,
|
||||
UserModule,
|
||||
OAuth2Module,
|
||||
OAuth2ClientModule,
|
||||
MulterModule.registerAsync({
|
||||
imports: [ConfigurationModule],
|
||||
useFactory: async (config: ConfigurationService) => {
|
||||
|
@ -42,7 +42,7 @@ export class TwoFactorController {
|
||||
const qrcode = await this.qr.createQRDataURI(url);
|
||||
|
||||
res.render('two-factor/activate', {
|
||||
...this.form.populateTemplate(req, session),
|
||||
...this.form.populateTemplate(req),
|
||||
qrcode,
|
||||
});
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
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 {
|
||||
OAuth2ClientURL,
|
||||
@ -18,6 +19,98 @@ export class OAuth2ClientService {
|
||||
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> {
|
||||
let client: OAuth2Client;
|
||||
|
||||
|
@ -77,6 +77,12 @@ export class UserService {
|
||||
return user;
|
||||
}
|
||||
|
||||
public async deleteAvatar(user: User): Promise<void> {
|
||||
if (user.picture) {
|
||||
await this.upload.delete(user.picture);
|
||||
}
|
||||
}
|
||||
|
||||
public async comparePasswords(
|
||||
hash: string,
|
||||
password: string,
|
||||
|
@ -1,6 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
import { SessionData } from 'express-session';
|
||||
|
||||
@Injectable()
|
||||
export class FormUtilityService {
|
||||
@ -25,7 +24,6 @@ export class FormUtilityService {
|
||||
*/
|
||||
public populateTemplate(
|
||||
req: Request,
|
||||
session: SessionData,
|
||||
additional: Record<string, any> = {},
|
||||
): Record<string, any> {
|
||||
const message = req.flash('message')[0] || {};
|
||||
@ -35,7 +33,7 @@ export class FormUtilityService {
|
||||
|
||||
return {
|
||||
path: req.originalUrl,
|
||||
csrf: session.csrf,
|
||||
csrf: req.session.csrf,
|
||||
message,
|
||||
form,
|
||||
...additional,
|
||||
|
@ -12,12 +12,20 @@ block body
|
||||
.authorize
|
||||
.authorize__user
|
||||
.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
|
||||
span.authorize__user-title #{user.display_name}
|
||||
span.authorize__user-user @#{user.username}
|
||||
.authorize__center
|
||||
.authorize__client
|
||||
.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
|
||||
span.authorize__client-title #{client.title}
|
||||
span.authorize__client-description #{client.description}
|
||||
@ -45,12 +53,12 @@ block body
|
||||
|
||||
form(method="POST", action="")
|
||||
div.form-container
|
||||
input(type="hidden", name="csrf", value=csrf)
|
||||
input(type="hidden", name="_csrf", value=csrf)
|
||||
input(type="hidden", name="decision", value="1")
|
||||
button.btn.btn-primary(type="submit") Authorize
|
||||
|
||||
form(method="POST", action="")
|
||||
div.form-container
|
||||
input(type="hidden", name="csrf", value=csrf)
|
||||
input(type="hidden", name="_csrf", value=csrf)
|
||||
input(type="hidden", name="decision", value="0")
|
||||
button.btn.btn-link(type="submit") Reject
|
||||
|
@ -18,7 +18,7 @@ block body
|
||||
|
||||
form(method="post")
|
||||
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
|
||||
input.form-control#username(type="text", name="username", placeholder="Username", autofocus, value=form.username)
|
||||
label.form-label(for="password") Password
|
||||
|
@ -19,7 +19,7 @@ block body
|
||||
|
||||
form(method="post")
|
||||
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
|
||||
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.
|
||||
@ -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.
|
||||
form(method="post")
|
||||
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
|
||||
input.form-control#email(type="email", name="email", autofocus, placeholder="Email addres")
|
||||
button.btn.btn-primary(type="submit") Send recovery email
|
||||
|
@ -18,7 +18,7 @@ block body
|
||||
|
||||
form(method="post")
|
||||
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
|
||||
input.form-control#totp(type="text", name="totp", autofocus, placeholder="xxxxxx")
|
||||
button.btn.btn-primary(type="submit") Log in
|
||||
|
@ -18,7 +18,7 @@ block body
|
||||
|
||||
form(method="post")
|
||||
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
|
||||
input.form-control#username(type="text", name="username", placeholder="Username", autofocus, value=form.username)
|
||||
|
@ -16,7 +16,7 @@ block settings
|
||||
.col
|
||||
form(method="post")
|
||||
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
|
||||
input.form-control#username(type="text", name="username", placeholder="Username", disabled, value=user.username)
|
||||
label.form-label(for="display_name") Display Name
|
||||
@ -33,16 +33,18 @@ block settings
|
||||
.flex-column(data-script="flex")
|
||||
button.btn.btn-primary(data-modal-trigger="avatar") Change avatar
|
||||
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")
|
||||
div.form-container
|
||||
input#csrf(type="hidden", name="csrf", value=csrf)
|
||||
input(type="hidden", name="_csrf", value=csrf)
|
||||
label.form-label(for="image") Image
|
||||
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.
|
||||
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__title
|
||||
|Change avatar
|
||||
|
@ -7,10 +7,10 @@ block body
|
||||
nav.sidebar.settings__nav
|
||||
ul
|
||||
li
|
||||
a(href="/account/general") General
|
||||
a(href="/account/general", class=path === '/account/general' ? 'active' : '') General
|
||||
li
|
||||
a(href="/account/oauth2") Authorizations
|
||||
a(href="/account/oauth2", class=path === '/account/oauth2' ? 'active' : '') Authorizations
|
||||
li
|
||||
a(href="/account/security") Security
|
||||
a(href="/account/security", class=path === '/account/security' ? 'active' : '') Security
|
||||
section.content.settings__content
|
||||
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")
|
||||
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
|
||||
input.form-control#code(type="text", name="code", autofocus, placeholder="xxxxxx")
|
||||
button.btn.btn-primary(type="submit") Activate
|
||||
|
Reference in New Issue
Block a user