feat(api): user authentication

This commit is contained in:
Julian Tölle 2020-02-01 16:11:48 +01:00
parent f14eda16ac
commit f253a66f86
41 changed files with 657 additions and 338 deletions

4
.dockerignore Normal file
View file

@ -0,0 +1,4 @@
node_modules
dist
frontend/node_modules

1
.gitignore vendored
View file

@ -2,6 +2,7 @@
/dist
/node_modules
# Logs
logs
*.log

View file

@ -26,7 +26,7 @@ services:
- 3000
labels:
- "traefik.enable=true" # Enable reverse-proxy for this service
- "traefik.http.routers.api.rule=PathPrefix(`/api/v1`)"
- "traefik.http.routers.api.rule=PathPrefix(`/api`)"
- "traefik.http.routers.api.entrypoints=web"
networks:
- web
@ -52,7 +52,7 @@ services:
proxy:
image: traefik
command:
- --log.level=debug
#- --log.level=debug
#- --accesslog=true
- --api # Enables the web UI
- --api.insecure=true

426
package-lock.json generated
View file

@ -615,6 +615,38 @@
"uuid": "3.4.0"
}
},
"@nestjs/jwt": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@nestjs/jwt/-/jwt-6.1.1.tgz",
"integrity": "sha512-XZEYC+p69N+Accktjho0B98TkwAKCZNt91+t08eukw7Gwk6FvfJB+aBzHCmQEaWUiAOpAo4eObgac86P12XOkw==",
"requires": {
"@types/jsonwebtoken": "7.2.8",
"jsonwebtoken": "8.4.0"
},
"dependencies": {
"jsonwebtoken": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.4.0.tgz",
"integrity": "sha512-coyXjRTCy0pw5WYBpMvWOMN+Kjaik2MwTUIq9cna/W7NpO9E+iYbumZONAz3hcr+tXFJECoQVrtmIoC3Oz0gvg==",
"requires": {
"jws": "^3.1.5",
"lodash.includes": "^4.3.0",
"lodash.isboolean": "^3.0.3",
"lodash.isinteger": "^4.0.4",
"lodash.isnumber": "^3.0.3",
"lodash.isplainobject": "^4.0.6",
"lodash.isstring": "^4.0.1",
"lodash.once": "^4.0.0",
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"@nestjs/passport": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/@nestjs/passport/-/passport-6.1.1.tgz",
@ -632,6 +664,15 @@
"tslib": "1.10.0"
}
},
"@nestjs/schedule": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@nestjs/schedule/-/schedule-0.2.0.tgz",
"integrity": "sha512-VkPPQYzsKexltKYf3yE2f1ZmUebGlkd4hfJ8kNtzLYfcfn4ZN+nn7zsJ0yyNvWlCeq3oD1cFLW3Y5qoS4+Glag==",
"requires": {
"cron": "1.8.2",
"uuid": "3.4.0"
}
},
"@nestjs/schematics": {
"version": "6.8.2",
"resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-6.8.2.tgz",
@ -643,6 +684,15 @@
"fs-extra": "8.1.0"
}
},
"@nestjs/swagger": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/@nestjs/swagger/-/swagger-4.2.3.tgz",
"integrity": "sha512-5QFy6/PRFG65m0FO1VR9XYHtXo6fdbqXJlkQKw4QmtTmIdcGgVQcByeBafE+OTArWYlDCnpMTI5G1Rh7OPGhlQ==",
"requires": {
"lodash": "4.17.15",
"path-to-regexp": "3.2.0"
}
},
"@nestjs/testing": {
"version": "6.11.1",
"resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-6.11.1.tgz",
@ -739,6 +789,7 @@
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.17.1.tgz",
"integrity": "sha512-RoX2EZjMiFMjZh9lmYrwgoP9RTpAjSHiJxdp4oidAQVO02T7HER3xj9UKue5534ULWeqVEkujhWcyvUce+d68w==",
"dev": true,
"requires": {
"@types/connect": "*",
"@types/node": "*"
@ -753,6 +804,7 @@
"version": "3.4.33",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.33.tgz",
"integrity": "sha512-2+FrkXY4zllzTNfJth7jOqEHC+enpLeGslEhpnTAkg21GkRrWV4SsAtqchtT4YS9/nODBU2/ZfsBY2X4J/dX7A==",
"dev": true,
"requires": {
"@types/node": "*"
}
@ -767,38 +819,23 @@
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.2.tgz",
"integrity": "sha512-5mHFNyavtLoJmnusB8OKJ5bshSzw+qkMIBAobLrIM48HJvunFva9mOa6aBwh64lBFyNwBbs0xiEFuj4eU/NjCA==",
"dev": true,
"requires": {
"@types/body-parser": "*",
"@types/express-serve-static-core": "*",
"@types/serve-static": "*"
}
},
"@types/express-jwt": {
"version": "0.0.42",
"resolved": "https://registry.npmjs.org/@types/express-jwt/-/express-jwt-0.0.42.tgz",
"integrity": "sha512-WszgUddvM1t5dPpJ3LhWNH8kfNN8GPIBrAGxgIYXVCEGx6Bx4A036aAuf/r5WH9DIEdlmp7gHOYvSM6U87B0ag==",
"requires": {
"@types/express": "*",
"@types/express-unless": "*"
}
},
"@types/express-serve-static-core": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.2.tgz",
"integrity": "sha512-El9yMpctM6tORDAiBwZVLMcxoTMcqqRO9dVyYcn7ycLWbvR8klrDn8CAOwRfZujZtWD7yS/mshTdz43jMOejbg==",
"dev": true,
"requires": {
"@types/node": "*",
"@types/range-parser": "*"
}
},
"@types/express-unless": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@types/express-unless/-/express-unless-0.5.1.tgz",
"integrity": "sha512-5fuvg7C69lemNgl0+v+CUxDYWVPSfXHhJPst4yTLcqi4zKJpORCxnDrnnilk3k0DTq/WrAUdvXFs01+vUqUZHw==",
"requires": {
"@types/express": "*"
}
},
"@types/istanbul-lib-coverage": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz",
@ -839,25 +876,66 @@
"integrity": "sha1-7ihweulOEdK4J7y+UnC86n8+ce4=",
"dev": true
},
"@types/jsonwebtoken": {
"version": "7.2.8",
"resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-7.2.8.tgz",
"integrity": "sha512-XENN3YzEB8D6TiUww0O8SRznzy1v+77lH7UmuN54xq/IHIsyWjWOzZuFFTtoiRuaE782uAoRwBe/wwow+vQXZw==",
"requires": {
"@types/node": "*"
}
},
"@types/mime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/mime/-/mime-2.0.1.tgz",
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw=="
"integrity": "sha512-FwI9gX75FgVBJ7ywgnq/P7tw+/o1GUbtP0KzbtusLigAOgIgNISRK0ZPl4qertvXSIE8YbsVJueQ90cDt9YYyw==",
"dev": true
},
"@types/node": {
"version": "12.12.25",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.25.tgz",
"integrity": "sha512-nf1LMGZvgFX186geVZR1xMZKKblJiRfiASTHw85zED2kI1yDKHDwTKMdkaCbTlXoRKlGKaDfYywt+V0As30q3w=="
},
"@types/passport": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.2.tgz",
"integrity": "sha512-Pf39AYKf8q+YoONym3150cEwfUD66dtwHJWvbeOzKxnA0GZZ/vAXhNWv9vMhKyRQBQZiQyWQnhYBEBlKW6G8wg==",
"dev": true,
"requires": {
"@types/express": "*"
}
},
"@types/passport-jwt": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/passport-jwt/-/passport-jwt-3.0.3.tgz",
"integrity": "sha512-RlOCXiTitE8kazj9jZc6/BfGCSqnv2w/eYPDm3+3iNsquHn7ratu7oIUskZx9ZtnwMdpvdpy+Z/QYClocH5NvQ==",
"dev": true,
"requires": {
"@types/express": "*",
"@types/jsonwebtoken": "*",
"@types/passport-strategy": "*"
}
},
"@types/passport-strategy": {
"version": "0.2.35",
"resolved": "https://registry.npmjs.org/@types/passport-strategy/-/passport-strategy-0.2.35.tgz",
"integrity": "sha512-o5D19Jy2XPFoX2rKApykY15et3Apgax00RRLf0RUotPDUsYrQa7x4howLYr9El2mlUApHmCMv5CZ1IXqKFQ2+g==",
"dev": true,
"requires": {
"@types/express": "*",
"@types/passport": "*"
}
},
"@types/range-parser": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==",
"dev": true
},
"@types/serve-static": {
"version": "1.13.3",
"resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.3.tgz",
"integrity": "sha512-oprSwp094zOglVrXdlo/4bAHtKTAxX6VT8FOZlBKrmyLbNvE1zxZyJ6yikMVtHIvwP45+ZQGJn+FdXGKTozq0g==",
"dev": true,
"requires": {
"@types/express-serve-static-core": "*",
"@types/mime": "*"
@ -917,6 +995,11 @@
}
}
},
"@types/validator": {
"version": "10.11.3",
"resolved": "https://registry.npmjs.org/@types/validator/-/validator-10.11.3.tgz",
"integrity": "sha512-GKF2VnEkMmEeEGvoo03ocrP9ySMuX1ypKazIYMlsjfslfBMhOAtC5dmEWKdJioW4lJN7MZRS88kalTsVClyQ9w=="
},
"@types/webpack": {
"version": "4.41.3",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.3.tgz",
@ -1202,6 +1285,7 @@
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz",
"integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==",
"dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
@ -1324,6 +1408,7 @@
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
"dev": true,
"requires": {
"safer-buffer": "~2.1.0"
}
@ -1369,7 +1454,8 @@
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
"dev": true
},
"assign-symbols": {
"version": "1.0.0",
@ -1398,7 +1484,8 @@
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
"dev": true
},
"atob": {
"version": "2.1.2",
@ -1409,12 +1496,14 @@
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
"dev": true
},
"aws4": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.9.1.tgz",
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug=="
"integrity": "sha512-wMHVg2EOHaMRxbzgFJ9gtjOOCrI80OHLG14rxi28XwOW8ux6IiEbRCGGGqCtdAIg4FQCbW20k9RsT4y3gJlFug==",
"dev": true
},
"axios": {
"version": "0.19.2",
@ -1591,6 +1680,7 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"dev": true,
"requires": {
"tweetnacl": "^0.14.3"
}
@ -1952,7 +2042,8 @@
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
"dev": true
},
"chalk": {
"version": "2.4.2",
@ -2022,6 +2113,11 @@
"safe-buffer": "^5.0.1"
}
},
"class-transformer": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.2.3.tgz",
"integrity": "sha512-qsP+0xoavpOlJHuYsQJsN58HXSl8Jvveo+T37rEvCEeRfMWoytAyR0Ua/YsFgpM6AZYZ/og2PJwArwzJl1aXtQ=="
},
"class-utils": {
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz",
@ -2045,6 +2141,16 @@
}
}
},
"class-validator": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.11.0.tgz",
"integrity": "sha512-niAmmSPFku9xsnpYYrddy8NZRrCX3yyoZ/rgPKOilE5BG0Ma1eVCIxpR4X0LasL/6BzbYzsutG+mSbAXlh4zNw==",
"requires": {
"@types/validator": "10.11.3",
"google-libphonenumber": "^3.1.6",
"validator": "12.0.0"
}
},
"cli-color": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/cli-color/-/cli-color-2.0.0.tgz",
@ -2332,6 +2438,7 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@ -2550,6 +2657,14 @@
"sha.js": "^2.4.8"
}
},
"cron": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz",
"integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==",
"requires": {
"moment-timezone": "^0.5.x"
}
},
"cross-spawn": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@ -2616,6 +2731,7 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
@ -2731,7 +2847,8 @@
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
"dev": true
},
"depd": {
"version": "1.1.2",
@ -2864,6 +2981,7 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"dev": true,
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
@ -3274,41 +3392,6 @@
}
}
},
"express-session": {
"version": "1.17.0",
"resolved": "https://registry.npmjs.org/express-session/-/express-session-1.17.0.tgz",
"integrity": "sha512-t4oX2z7uoSqATbMfsxWMbNjAL0T5zpvcJCk3Z9wnPPN7ibddhnmDZXHfEcoBMG2ojKXZoCyPMc5FbtK+G7SoDg==",
"requires": {
"cookie": "0.4.0",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~2.0.0",
"on-headers": "~1.0.2",
"parseurl": "~1.3.3",
"safe-buffer": "5.2.0",
"uid-safe": "~2.1.5"
},
"dependencies": {
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
},
"safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
}
}
},
"ext": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz",
@ -3327,7 +3410,8 @@
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
"dev": true
},
"extend-shallow": {
"version": "3.0.2",
@ -3429,12 +3513,14 @@
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
"integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
"dev": true
},
"fast-diff": {
"version": "1.2.0",
@ -3445,7 +3531,8 @@
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
"dev": true
},
"fast-levenshtein": {
"version": "2.0.6",
@ -3619,7 +3706,8 @@
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
"dev": true
},
"fork-ts-checker-webpack-plugin": {
"version": "4.0.2",
@ -3640,6 +3728,7 @@
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
"dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
@ -4335,6 +4424,7 @@
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
@ -4379,6 +4469,11 @@
"integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
"dev": true
},
"google-libphonenumber": {
"version": "3.2.6",
"resolved": "https://registry.npmjs.org/google-libphonenumber/-/google-libphonenumber-3.2.6.tgz",
"integrity": "sha512-6QCQAaKJlSd/1dUqvdQf7zzfb3uiZHsG8yhCfOdCVRfMuPZ/VDIEB47y5SYwjPQJPs7ebfW5jj6PeobB9JJ4JA=="
},
"graceful-fs": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
@ -4394,12 +4489,14 @@
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
"dev": true
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
"dev": true,
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
@ -4538,6 +4635,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@ -4940,7 +5038,8 @@
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
"dev": true
},
"is-windows": {
"version": "1.0.2",
@ -4974,7 +5073,8 @@
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
"dev": true
},
"istanbul-lib-coverage": {
"version": "2.0.5",
@ -5538,7 +5638,8 @@
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"dev": true
},
"jsdom": {
"version": "11.12.0",
@ -5597,17 +5698,20 @@
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
"dev": true
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
"integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
"dev": true
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
"dev": true
},
"json5": {
"version": "1.0.1",
@ -5663,6 +5767,7 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"dev": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@ -5680,35 +5785,6 @@
"safe-buffer": "^5.0.1"
}
},
"jwks-rsa": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/jwks-rsa/-/jwks-rsa-1.6.2.tgz",
"integrity": "sha512-c/mFFq/wVXSkHzHGH+hLUwLeRKSCofNHJZKPzHho4YmO9LGwAazk7akfABvWhduS9OejWvqBS2jA69YeruEvNA==",
"requires": {
"@types/express-jwt": "0.0.42",
"debug": "^4.1.0",
"jsonwebtoken": "^8.5.1",
"limiter": "^1.1.4",
"lru-memoizer": "^2.0.1",
"ms": "^2.1.2",
"request": "^2.88.0"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"jws": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/jws/-/jws-3.2.2.tgz",
@ -5752,11 +5828,6 @@
"type-check": "~0.3.2"
}
},
"limiter": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.5.tgz",
"integrity": "sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA=="
},
"lines-and-columns": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz",
@ -5812,13 +5883,7 @@
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
"dev": true
},
"lodash.clonedeep": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
"integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8="
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash.debounce": {
"version": "4.0.8",
@ -5916,31 +5981,6 @@
"yallist": "^3.0.2"
}
},
"lru-memoizer": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/lru-memoizer/-/lru-memoizer-2.0.1.tgz",
"integrity": "sha512-kGl+zlIqdQL24f0Q9IUSUZeSvA7nqXPFLA7suFh00v4KVqfXkZJtkPfTfXV/oQMSPfNr6VT4xGkRAUPhFnGyxQ==",
"requires": {
"lodash.clonedeep": "^4.5.0",
"lru-cache": "~4.0.0"
},
"dependencies": {
"lru-cache": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.0.2.tgz",
"integrity": "sha1-HRdnnAac2l0ECZGgnbwsDbN35V4=",
"requires": {
"pseudomap": "^1.0.1",
"yallist": "^2.0.0"
}
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
}
}
},
"lru-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/lru-queue/-/lru-queue-0.1.0.tgz",
@ -6223,6 +6263,19 @@
"minimist": "0.0.8"
}
},
"moment": {
"version": "2.24.0",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
"integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg=="
},
"moment-timezone": {
"version": "0.5.27",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.27.tgz",
"integrity": "sha512-EIKQs7h5sAsjhPCqN6ggx6cEbs94GK050254TIJySD1bzoM5JTYDwAU1IoVOeTOL6Gm27kYJ51/uuvq1kIlrbw==",
"requires": {
"moment": ">= 2.9.0"
}
},
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@ -6268,11 +6321,6 @@
"xtend": "^4.0.0"
}
},
"mustache": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.0.tgz",
"integrity": "sha512-FJgjyX/IVkbXBXYUwH+OYwQKqWpFPLaLVESd70yHjSDunwzV2hZOoTBvPf4KLoxesUzzyfTH6F784Uqd7Wm5yA=="
},
"mute-stream": {
"version": "0.0.7",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
@ -6538,7 +6586,8 @@
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
"dev": true
},
"object-assign": {
"version": "4.1.1",
@ -6641,11 +6690,6 @@
"ee-first": "1.1.1"
}
},
"on-headers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz",
"integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -6994,16 +7038,6 @@
"pause": "0.0.1"
}
},
"passport-auth0": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/passport-auth0/-/passport-auth0-1.3.1.tgz",
"integrity": "sha512-HT7qbvw1dlRrNAIsisgsACaiDBcLLBMUFIFCt4dZ4vLrJF8bkNWj9QgeTAcXjQoYJ5sXfpaD/wYUGBSe98cG0g==",
"requires": {
"passport-oauth": "^1.0.0",
"passport-oauth2": "^1.5.0",
"request": "^2.88.0"
}
},
"passport-jwt": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/passport-jwt/-/passport-jwt-4.0.0.tgz",
@ -7044,6 +7078,16 @@
"utils-merge": "1.x.x"
}
},
"passport-spotify": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/passport-spotify/-/passport-spotify-1.1.0.tgz",
"integrity": "sha512-jLVtH7Z+5XW854H0mAsdpGJq5dMSwxQQu1jcXhq+3JlB8HuP5xkCLMuQ5K24S7WYh+cAHLrEKJPAoeo1D8CbUg==",
"requires": {
"passport-oauth": "1.0.0",
"querystring": "~0.2.0",
"util": "~0.11.0"
}
},
"passport-strategy": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz",
@ -7126,7 +7170,8 @@
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
"dev": true
},
"pg": {
"version": "7.17.1",
@ -7329,15 +7374,11 @@
"integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=",
"dev": true
},
"pseudomap": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
"integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
},
"psl": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.7.0.tgz",
"integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ=="
"integrity": "sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==",
"dev": true
},
"public-encrypt": {
"version": "4.0.3",
@ -7389,7 +7430,8 @@
"punycode": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
"dev": true
},
"qs": {
"version": "6.7.0",
@ -7399,8 +7441,7 @@
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
"dev": true
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
},
"querystring-es3": {
"version": "0.2.1",
@ -7408,11 +7449,6 @@
"integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=",
"dev": true
},
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs="
},
"randombytes": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
@ -7575,6 +7611,7 @@
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
@ -7601,17 +7638,20 @@
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
"dev": true
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
"dev": true
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"dev": true,
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
@ -8186,6 +8226,7 @@
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
"dev": true,
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
@ -8527,6 +8568,19 @@
"has-flag": "^3.0.0"
}
},
"swagger-ui-dist": {
"version": "3.25.0",
"resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.25.0.tgz",
"integrity": "sha512-vwvJPPbdooTvDwLGzjIXinOXizDJJ6U1hxnJL3y6U3aL1d2MSXDmKg2139XaLBhsVZdnQJV2bOkX4reB+RXamg=="
},
"swagger-ui-express": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/swagger-ui-express/-/swagger-ui-express-4.1.3.tgz",
"integrity": "sha512-f8SEn4YWkKh/HGK0ZjuA2VqA78i1aY6OIa5cqYNgOkBobfHV6Mz4dphQW/us8HYhEFfbENq329PyfIonWfzFrw==",
"requires": {
"swagger-ui-dist": "^3.18.1"
}
},
"symbol-observable": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz",
@ -9021,6 +9075,7 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"dev": true,
"requires": {
"safe-buffer": "^5.0.1"
}
@ -9028,7 +9083,8 @@
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"dev": true
},
"type": {
"version": "1.2.0",
@ -9120,14 +9176,6 @@
"integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==",
"dev": true
},
"uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"requires": {
"random-bytes": "~1.0.0"
}
},
"uid2": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/uid2/-/uid2-0.0.3.tgz",
@ -9230,6 +9278,7 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
"dev": true,
"requires": {
"punycode": "^2.1.0"
}
@ -9268,7 +9317,6 @@
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
"integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==",
"dev": true,
"requires": {
"inherits": "2.0.3"
}
@ -9310,6 +9358,11 @@
"spdx-expression-parse": "^3.0.0"
}
},
"validator": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-12.0.0.tgz",
"integrity": "sha512-r5zA1cQBEOgYlesRmSEwc9LkbfNLTtji+vWyaHzRZUxCTHdsX3bd+sdHfs5tGZ2W6ILGGsxWxCNwT/h3IY/3ng=="
},
"vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
@ -9319,6 +9372,7 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"dev": true,
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",

View file

@ -23,19 +23,22 @@
"@nestjs/common": "^6.7.2",
"@nestjs/config": "^0.1.0",
"@nestjs/core": "^6.7.2",
"@nestjs/jwt": "^6.1.1",
"@nestjs/passport": "^6.1.1",
"@nestjs/platform-express": "^6.7.2",
"@nestjs/schedule": "^0.2.0",
"@nestjs/swagger": "^4.2.3",
"@nestjs/typeorm": "^6.2.0",
"express-session": "^1.17.0",
"jwks-rsa": "^1.6.2",
"mustache": "^4.0.0",
"class-transformer": "^0.2.3",
"class-validator": "^0.11.0",
"passport": "^0.4.1",
"passport-auth0": "^1.3.1",
"passport-jwt": "^4.0.0",
"passport-spotify": "^1.1.0",
"pg": "^7.17.1",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.0",
"rxjs": "^6.5.3",
"swagger-ui-express": "^4.1.3",
"typeorm": "^0.2.22"
},
"devDependencies": {
@ -45,6 +48,7 @@
"@types/express": "^4.17.1",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.5",
"@types/passport-jwt": "^3.0.3",
"@types/supertest": "^2.0.8",
"jest": "^24.9.0",
"prettier": "^1.18.2",

View file

@ -1,17 +1,17 @@
import { Module } from "@nestjs/common";
import { ConfigModule } from "@nestjs/config";
import { AuthenticationModule } from "./authentication/authentication.module";
import { AuthModule } from "./auth/auth.module";
import { DatabaseModule } from "./database/database.module";
import { ConnectionsModule } from "./connections/connections.module";
import { FrontendModule } from './frontend/frontend.module';
import { SourcesModule } from "./sources/sources.module";
import { UsersModule } from "./users/users.module";
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
DatabaseModule,
AuthenticationModule,
ConnectionsModule,
FrontendModule
AuthModule,
UsersModule,
SourcesModule
]
})
export class AppModule {}

View file

@ -0,0 +1,40 @@
import { Controller, Get, Res, UseGuards } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { Response } from "express";
import { User } from "../users/user.entity";
import { ReqUser } from "./decorators/req-user.decorator";
import { AuthService } from "./auth.service";
import { ConfigService } from "@nestjs/config";
@Controller("api/v1/auth")
export class AuthController {
constructor(
private readonly authService: AuthService,
private readonly config: ConfigService
) {}
@Get("spotify")
@UseGuards(AuthGuard("spotify"))
spotifyRedirect() {
// User is redirected by AuthGuard
}
@Get("spotify/callback")
@UseGuards(AuthGuard("spotify"))
async spotifyCallback(@ReqUser() user: User, @Res() res: Response) {
const { accessToken } = await this.authService.createToken(user);
// Transmit accessToken to Frontend
res.cookie("listory_access_token", accessToken, {
// SPA will directly read cookie, save it to local storage and delete it
// 15 Minutes should be enough
maxAge: 15 * 60 * 1000,
// Must be readable by SPA
httpOnly: false
});
// Redirect User to SPA
res.redirect("/");
}
}

27
src/auth/auth.module.ts Normal file
View file

@ -0,0 +1,27 @@
import { Module } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { JwtModule } from "@nestjs/jwt";
import { PassportModule } from "@nestjs/passport";
import { UsersModule } from "../users/users.module";
import { AuthController } from "./auth.controller";
import { AuthService } from "./auth.service";
import { JwtStrategy } from "./jwt.strategy";
import { SpotifyStrategy } from "./spotify.strategy";
@Module({
imports: [
PassportModule.register({ defaultStrategy: "jwt" }),
JwtModule.registerAsync({
useFactory: (config: ConfigService) => ({
secret: config.get<string>("JWT_SECRET"),
signOptions: { expiresIn: config.get<string>("JWT_EXPIRATION_TIME") }
}),
inject: [ConfigService]
}),
UsersModule
],
providers: [AuthService, SpotifyStrategy, JwtStrategy],
exports: [PassportModule],
controllers: [AuthController]
})
export class AuthModule {}

View file

@ -1,15 +1,15 @@
import { Test, TestingModule } from "@nestjs/testing";
import { ConnectionsService } from "./connections.service";
import { AuthService } from "./auth.service";
describe("ConnectionsService", () => {
let service: ConnectionsService;
describe("AuthService", () => {
let service: AuthService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ConnectionsService]
providers: [AuthService]
}).compile();
service = module.get<ConnectionsService>(ConnectionsService);
service = module.get<AuthService>(AuthService);
});
it("should be defined", () => {

47
src/auth/auth.service.ts Normal file
View file

@ -0,0 +1,47 @@
import { Injectable } from "@nestjs/common";
import { User } from "../users/user.entity";
import { UsersService } from "../users/users.service";
import { LoginDto } from "./dto/login.dto";
import { JwtService } from "@nestjs/jwt";
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService
) {}
async spotifyLogin({
accessToken,
refreshToken,
profile
}: LoginDto): Promise<User> {
const user = await this.usersService.createOrUpdate({
displayName: profile.displayName,
photo: profile.photos.length > 0 ? profile.photos[0] : null,
spotify: {
id: profile.id,
accessToken,
refreshToken
}
});
return user;
}
async createToken(user: User): Promise<{ accessToken }> {
const payload = {
sub: user.id,
name: user.displayName,
picture: user.photo
};
const token = await this.jwtService.signAsync(payload);
return { accessToken: token };
}
async findUser(id: string): Promise<User> {
return this.usersService.findById(id);
}
}

View file

@ -0,0 +1,11 @@
import { applyDecorators, UseGuards } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
import { ApiBearerAuth, ApiUnauthorizedResponse } from "@nestjs/swagger";
export function Auth() {
return applyDecorators(
UseGuards(AuthGuard("jwt")),
ApiBearerAuth(),
ApiUnauthorizedResponse({ description: 'Unauthorized"' })
);
}

View file

@ -0,0 +1,3 @@
import { createParamDecorator } from "@nestjs/common";
export const ReqUser = createParamDecorator((data, req) => req.user);

View file

@ -0,0 +1,9 @@
export class LoginDto {
accessToken: string;
refreshToken: string;
profile: {
id: string;
displayName: string;
photos: string[];
};
}

23
src/auth/jwt.strategy.ts Normal file
View file

@ -0,0 +1,23 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy, ExtractJwt } from "passport-jwt";
import { AuthService } from "./auth.service";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly config: ConfigService,
private readonly authService: AuthService
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
ignoreExpiration: false,
secretOrKey: config.get<string>("JWT_SECRET")
});
}
async validate(payload: any) {
return this.authService.findUser(payload.sub);
}
}

View file

@ -0,0 +1,33 @@
import { Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { PassportStrategy } from "@nestjs/passport";
import { Strategy } from "passport-spotify";
import { AuthService } from "./auth.service";
@Injectable()
export class SpotifyStrategy extends PassportStrategy(Strategy) {
constructor(
private readonly config: ConfigService,
private readonly authService: AuthService
) {
super({
clientID: config.get<string>("SPOTIFY_CLIENT_ID"),
clientSecret: config.get<string>("SPOTIFY_CLIENT_SECRET"),
callbackURL: `${config.get<string>("BASE_DOMAIN") ||
"http://localhost:3000"}/api/v1/auth/spotify/callback`,
scope: [
"user-read-private",
"user-read-email",
"user-read-recently-played"
]
});
}
async validate(accessToken: string, refreshToken: string, profile: any) {
return await this.authService.spotifyLogin({
accessToken,
refreshToken,
profile
});
}
}

View file

@ -1,10 +0,0 @@
import { Module } from "@nestjs/common";
import { PassportModule } from "@nestjs/passport";
import { JwtStrategy } from "./jwt.strategy";
@Module({
imports: [PassportModule.register({ defaultStrategy: "jwt" })],
providers: [JwtStrategy],
exports: [PassportModule]
})
export class AuthenticationModule {}

View file

@ -1,28 +0,0 @@
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
import { passportJwtSecret } from "jwks-rsa";
import { ExtractJwt, Strategy } from "passport-jwt";
import { ConfigService } from "@nestjs/config";
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(private readonly config: ConfigService) {
super({
secretOrKeyProvider: passportJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${config.get<string>("AUTH0_DOMAIN")}.well-known/jwks.json`
}),
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
audience: config.get<string>("AUTH0_AUDIENCE"),
issuer: config.get<string>("AUTH0_DOMAIN"),
algorithms: ["RS256"]
});
}
validate(payload: any) {
return payload;
}
}

View file

@ -1,3 +0,0 @@
export enum ConnectionType {
SPOTIFY = "spotify"
}

View file

@ -1,14 +0,0 @@
import { Entity, Column, PrimaryGeneratedColumn } from "typeorm";
import { ConnectionType } from "./connection-type.enum";
@Entity()
export class Connection {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column()
userID: string;
@Column()
type: ConnectionType;
}

View file

@ -1,18 +0,0 @@
import { Test, TestingModule } from "@nestjs/testing";
import { ConnectionsController } from "./connections.controller";
describe("Connections Controller", () => {
let controller: ConnectionsController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [ConnectionsController]
}).compile();
controller = module.get<ConnectionsController>(ConnectionsController);
});
it("should be defined", () => {
expect(controller).toBeDefined();
});
});

View file

@ -1,11 +0,0 @@
import { Controller, Get, UseGuards } from "@nestjs/common";
import { AuthGuard } from "@nestjs/passport";
@Controller("api/v1/connections")
export class ConnectionsController {
@Get()
@UseGuards(AuthGuard("jwt"))
get() {
return { msg: "Success!" };
}
}

View file

@ -1,12 +0,0 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { ConnectionsController } from "./connections.controller";
import { ConnectionsRepository } from "./connections.repository";
import { ConnectionsService } from "./connections.service";
@Module({
imports: [TypeOrmModule.forFeature([ConnectionsRepository])],
controllers: [ConnectionsController],
providers: [ConnectionsService]
})
export class ConnectionsModule {}

View file

@ -1,5 +0,0 @@
import { EntityRepository, Repository } from "typeorm";
import { Connection } from "./connection.entity";
@EntityRepository(Connection)
export class ConnectionsRepository extends Repository<Connection> {}

View file

@ -1,7 +0,0 @@
import { Injectable } from "@nestjs/common";
import { ConnectionsRepository } from "./connections.repository";
@Injectable()
export class ConnectionsService {
constructor(private readonly connectionRepository: ConnectionsRepository) {}
}

View file

@ -1,4 +1,4 @@
import { ConfigModule, ConfigService } from "@nestjs/config";
import { ConfigService } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";
import { join } from "path";
@ -17,9 +17,9 @@ export const DatabaseModule = TypeOrmModule.forRootAsync({
entities: [join(__dirname, "..", "**/*.entity.{ts,js}")],
// Migrations
migrationsRun: true,
migrations: [join(__dirname, "migrations", "*.{ts,js}")],
synchronize: false
// migrationsRun: true,
// migrations: [join(__dirname, "migrations", "*.{ts,js}")],
synchronize: true
}),
inject: [ConfigService]
});

View file

@ -1,7 +0,0 @@
import { Controller, Get } from "@nestjs/common";
@Controller("")
export class FrontendController {
@Get()
index() {}
}

View file

@ -1,7 +0,0 @@
import { Module } from '@nestjs/common';
import { FrontendController } from './frontend.controller';
@Module({
controllers: [FrontendController]
})
export class FrontendModule {}

View file

@ -1,14 +1,23 @@
import { NestFactory } from "@nestjs/core";
import { NestExpressApplication } from "@nestjs/platform-express";
import { join } from "path";
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
import { AppModule } from "./app.module";
async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);
app.useStaticAssets(join(__dirname, "frontend", "public"));
app.setBaseViewsDir(join(__dirname, "frontend", "views"));
app.setViewEngine("mustache");
// Setup API Docs
const options = new DocumentBuilder()
.setTitle("Listory")
.setDescription("Track and analyze your Spotify Listens")
.setVersion("1.0")
.addBearerAuth()
.addTag("user")
.addTag("listens")
.addTag("auth")
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup("api/docs", app, document);
await app.listen(3000);
}

View file

@ -0,0 +1,7 @@
import { Module } from '@nestjs/common';
import { SpotifyModule } from './spotify/spotify.module';
@Module({
imports: [SpotifyModule]
})
export class SourcesModule {}

View file

@ -0,0 +1,12 @@
import { Column } from "typeorm";
export class SpotifyConnection {
@Column()
id: string;
@Column()
accessToken: string;
@Column()
refreshToken: string;
}

View file

@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { SpotifyService } from './spotify.service';
import { SpotifyApiModule } from './spotify-api/spotify-api.module';
@Module({
providers: [SpotifyService],
imports: [SpotifyApiModule]
})
export class SpotifyModule {}

View file

@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { SpotifyService } from './spotify.service';
describe('SpotifyService', () => {
let service: SpotifyService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [SpotifyService],
}).compile();
service = module.get<SpotifyService>(SpotifyService);
});
it('should be defined', () => {
expect(service).toBeDefined();
});
});

View file

@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
import {Interval} from "@nestjs/cron"
@Injectable()
export class SpotifyService {
@Interval
}

View file

@ -0,0 +1,10 @@
export class CreateOrUpdateDto {
displayName: string;
photo?: string;
spotify: {
id: string;
accessToken: string;
refreshToken: string;
};
}

17
src/users/user.entity.ts Normal file
View file

@ -0,0 +1,17 @@
import { Entity, Column, PrimaryGeneratedColumn, OneToOne } from "typeorm";
import { SpotifyConnection } from "../sources/spotify/spotify-connection.entity";
@Entity()
export class User {
@PrimaryGeneratedColumn("uuid")
id: string;
@Column()
displayName: string;
@Column({ nullable: true })
photo?: string;
@Column(type => SpotifyConnection)
spotify: SpotifyConnection;
}

View file

@ -0,0 +1,5 @@
import { EntityRepository, Repository } from "typeorm";
import { User } from "./user.entity";
@EntityRepository(User)
export class UserRepository extends Repository<User> {}

View file

@ -1,15 +1,15 @@
import { Test, TestingModule } from '@nestjs/testing';
import { FrontendController } from './frontend.controller';
import { UsersController } from './users.controller';
describe('Frontend Controller', () => {
let controller: FrontendController;
describe('Users Controller', () => {
let controller: UsersController;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
controllers: [FrontendController],
controllers: [UsersController],
}).compile();
controller = module.get<FrontendController>(FrontendController);
controller = module.get<UsersController>(UsersController);
});
it('should be defined', () => {

View file

@ -0,0 +1,17 @@
import { Controller, Get } from "@nestjs/common";
import { Auth } from "../auth/decorators/auth.decorator";
import { ReqUser } from "../auth/decorators/req-user.decorator";
import { User } from "./user.entity";
@Controller("api/v1/users")
export class UsersController {
@Get("me")
@Auth()
getMe(@ReqUser() user: User): Omit<User, "spotify"> {
return {
id: user.id,
displayName: user.displayName,
photo: user.photo
};
}
}

13
src/users/users.module.ts Normal file
View file

@ -0,0 +1,13 @@
import { Module } from "@nestjs/common";
import { TypeOrmModule } from "@nestjs/typeorm";
import { UserRepository } from "./user.repository";
import { UsersService } from "./users.service";
import { UsersController } from './users.controller';
@Module({
imports: [TypeOrmModule.forFeature([UserRepository])],
providers: [UsersService],
exports: [UsersService],
controllers: [UsersController]
})
export class UsersModule {}

View file

@ -0,0 +1,18 @@
import { Test, TestingModule } from "@nestjs/testing";
import { UsersService } from "./users.service";
describe("UsersService", () => {
let service: UsersService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [UsersService]
}).compile();
service = module.get<UsersService>(UsersService);
});
it("should be defined", () => {
expect(service).toBeDefined();
});
});

View file

@ -0,0 +1,42 @@
import { Injectable, NotFoundException } from "@nestjs/common";
import { CreateOrUpdateDto } from "./dto/create-or-update.dto";
import { User } from "./user.entity";
import { UserRepository } from "./user.repository";
@Injectable()
export class UsersService {
constructor(private readonly userRepository: UserRepository) {}
async findById(id: string): Promise<User> {
const user = await this.userRepository.findOne(id);
if (!user) {
throw new NotFoundException("UserNotFound");
}
return user;
}
async createOrUpdate(data: CreateOrUpdateDto): Promise<User> {
let user = await this.userRepository.findOne({
where: { spotify: { id: data.spotify.id } }
});
if (!user) {
user = this.userRepository.create({
spotify: {
id: data.spotify.id
}
});
}
user.spotify.accessToken = data.spotify.accessToken;
user.spotify.refreshToken = data.spotify.refreshToken;
user.displayName = data.displayName;
user.photo = data.photo;
await this.userRepository.save(user);
return user;
}
}