Add website api login mutation
This commit is contained in:
parent
c106d7607e
commit
9007437bd3
19 changed files with 385 additions and 27 deletions
|
@ -29,6 +29,10 @@
|
|||
"no-console": "off",
|
||||
"max-len": ["off"],
|
||||
"import/first": ["off"],
|
||||
"max-classes-per-file": ["off"]
|
||||
"max-classes-per-file": ["off"],
|
||||
"@typescript-eslint/consistent-type-imports": ["error", {
|
||||
"prefer": "type-imports",
|
||||
"disallowTypeAnnotations": true
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
1
backend/.gitignore
vendored
1
backend/.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
node_modules
|
||||
.env
|
||||
dist
|
||||
secrets
|
||||
|
|
193
backend/package-lock.json
generated
193
backend/package-lock.json
generated
|
@ -578,6 +578,20 @@
|
|||
"tslib": "^1.9.3"
|
||||
}
|
||||
},
|
||||
"@wulkanowy/sdk": {
|
||||
"version": "0.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@wulkanowy/sdk/-/sdk-0.1.1.tgz",
|
||||
"integrity": "sha512-ktBYmd4nNeAs5STH9rf9EaAxPxSCN2PWtuP+rqW980RcWIv6IJddYvW1j8u60VFoThxMPWMpRHr7MeRqjKaC4A==",
|
||||
"requires": {
|
||||
"axios": "^0.21.1",
|
||||
"axios-cookiejar-support": "^1.0.1",
|
||||
"cheerio": "github:dominik-korsa/cheerio#export-types",
|
||||
"date-fns": "^2.16.1",
|
||||
"date-fns-tz": "^1.0.12",
|
||||
"querystring": "^0.2.0",
|
||||
"tough-cookie": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"abstract-logging": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-2.0.1.tgz",
|
||||
|
@ -979,6 +993,30 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz",
|
||||
"integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.10.0"
|
||||
}
|
||||
},
|
||||
"axios-cookiejar-support": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.1.tgz",
|
||||
"integrity": "sha512-IZJxnAJ99XxiLqNeMOqrPbfR7fRyIfaoSLdPUf4AMQEGkH8URs0ghJK/xtqBsD+KsSr3pKl4DEQjCn834pHMig==",
|
||||
"requires": {
|
||||
"is-redirect": "^1.0.0",
|
||||
"pify": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"pify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz",
|
||||
"integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"backo2": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz",
|
||||
|
@ -1033,6 +1071,11 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
|
@ -1154,6 +1197,37 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"cheerio": {
|
||||
"version": "github:dominik-korsa/cheerio#0204881418326ab4ddd4b68938c7de32580c9ae3",
|
||||
"from": "github:dominik-korsa/cheerio#export-types",
|
||||
"requires": {
|
||||
"cheerio-select": "^1.1.0",
|
||||
"dom-serializer": "~1.2.0",
|
||||
"domhandler": "^4.0.0",
|
||||
"htmlparser2": "^6.0.0",
|
||||
"parse5": "^6.0.1",
|
||||
"parse5-htmlparser2-tree-adapter": "^6.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"parse5": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
|
||||
"integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"cheerio-select": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-1.1.0.tgz",
|
||||
"integrity": "sha512-hOcy3Ps4lg6tMWM/q2NL5ItM+BTt1C8TNbtb9ZGscqDbGhKX0TzLRy9QJ2/KVlmmEbMjCNBublWdO4TPz51SdA==",
|
||||
"requires": {
|
||||
"css-select": "^3.1.2",
|
||||
"css-what": "^4.0.0",
|
||||
"domelementtype": "^2.1.0",
|
||||
"domhandler": "^4.0.0",
|
||||
"domutils": "^2.4.4"
|
||||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.0.tgz",
|
||||
|
@ -1272,6 +1346,23 @@
|
|||
"which": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"css-select": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-3.1.2.tgz",
|
||||
"integrity": "sha512-qmss1EihSuBNWNNhHjxzxSfJoFBM/lERB/Q4EnsJQQC62R2evJDW481091oAdOr9uh46/0n4nrg0It5cAnj1RA==",
|
||||
"requires": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^4.0.0",
|
||||
"domhandler": "^4.0.0",
|
||||
"domutils": "^2.4.3",
|
||||
"nth-check": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"css-what": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-4.0.0.tgz",
|
||||
"integrity": "sha512-teijzG7kwYfNVsUh2H/YN62xW3KK9YhXEgSlbxMlcyjPNvdKJqFx5lrwlJgoFP1ZHlB89iGDlo/JyshKeRhv5A=="
|
||||
},
|
||||
"cssfilter": {
|
||||
"version": "0.0.10",
|
||||
"resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz",
|
||||
|
@ -1286,6 +1377,16 @@
|
|||
"array-find-index": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"date-fns": {
|
||||
"version": "2.16.1",
|
||||
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.16.1.tgz",
|
||||
"integrity": "sha512-sAJVKx/FqrLYHAQeN7VpJrPhagZc9R4ImZIWYRFZaaohR3KzmuK88touwsSwSVT8Qcbd4zoDsnGfX4GFB4imyQ=="
|
||||
},
|
||||
"date-fns-tz": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/date-fns-tz/-/date-fns-tz-1.0.12.tgz",
|
||||
"integrity": "sha512-Ca+9pjGkU90XDHnclfSjz9o7g/ZqyYyYI0aCYmbf65P75oy8gktuaRslO3UPXl3ADgAnF9/KCykQkpU3/xvtWQ=="
|
||||
},
|
||||
"dateformat": {
|
||||
"version": "1.0.12",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.12.tgz",
|
||||
|
@ -1381,6 +1482,39 @@
|
|||
"esutils": "^2.0.2"
|
||||
}
|
||||
},
|
||||
"dom-serializer": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.2.0.tgz",
|
||||
"integrity": "sha512-n6kZFH/KlCrqs/1GHMOd5i2fd/beQHuehKdWvNNffbGHTr/almdhuVvTVFb3V7fglz+nC50fFusu3lY33h12pA==",
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.0.0",
|
||||
"entities": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"domelementtype": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.1.0.tgz",
|
||||
"integrity": "sha512-LsTgx/L5VpD+Q8lmsXSHW2WpA+eBlZ9HPf3erD1IoPF00/3JKHZ3BknUVA2QGDNu69ZNmyFmCWBSO45XjYKC5w=="
|
||||
},
|
||||
"domhandler": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.0.0.tgz",
|
||||
"integrity": "sha512-KPTbnGQ1JeEMQyO1iYXoagsI6so/C96HZiFyByU3T6iAzpXn8EGEvct6unm1ZGoed8ByO2oirxgwxBmqKF9haA==",
|
||||
"requires": {
|
||||
"domelementtype": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"domutils": {
|
||||
"version": "2.4.4",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-2.4.4.tgz",
|
||||
"integrity": "sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==",
|
||||
"requires": {
|
||||
"dom-serializer": "^1.0.1",
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"dotenv": {
|
||||
"version": "8.2.0",
|
||||
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz",
|
||||
|
@ -1417,6 +1551,11 @@
|
|||
"ansi-colors": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"entities": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz",
|
||||
"integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w=="
|
||||
},
|
||||
"error-ex": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
|
||||
|
@ -2041,6 +2180,11 @@
|
|||
"integrity": "sha512-tW+UkmtNg/jv9CSofAKvgVcO7c2URjhTdW1ZTkcAritblu8tajiYy7YisnIflEwtKssCtOxpnBRoCB7iap0/TA==",
|
||||
"dev": true
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz",
|
||||
"integrity": "sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg=="
|
||||
},
|
||||
"for-each": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
|
||||
|
@ -2274,6 +2418,17 @@
|
|||
"integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==",
|
||||
"dev": true
|
||||
},
|
||||
"htmlparser2": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.0.0.tgz",
|
||||
"integrity": "sha512-numTQtDZMoh78zJpaNdJ9MXb2cv5G3jwUoe3dMQODubZvLoGvTE/Ofp6sHvH8OGKcN/8A47pGLi/k58xHP/Tfw==",
|
||||
"requires": {
|
||||
"domelementtype": "^2.0.1",
|
||||
"domhandler": "^4.0.0",
|
||||
"domutils": "^2.4.4",
|
||||
"entities": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"http-errors": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz",
|
||||
|
@ -2419,6 +2574,11 @@
|
|||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true
|
||||
},
|
||||
"is-redirect": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz",
|
||||
"integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ="
|
||||
},
|
||||
"is-regex": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz",
|
||||
|
@ -2844,6 +3004,14 @@
|
|||
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
|
||||
"dev": true
|
||||
},
|
||||
"nth-check": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
|
||||
"integrity": "sha512-i4sc/Kj8htBrAiH1viZ0TgU8Y5XqCaV/FziYK6TBczxmeKm3AEFWqqF3195yKudrarqy7Zu80Ra5dobFjn9X/Q==",
|
||||
"requires": {
|
||||
"boolbase": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
|
@ -3132,6 +3300,11 @@
|
|||
"ipaddr.js": "1.9.1"
|
||||
}
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||
"integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ=="
|
||||
},
|
||||
"pump": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
|
||||
|
@ -3146,6 +3319,11 @@
|
|||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
|
||||
"integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
|
||||
},
|
||||
"querystring": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
|
||||
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
|
||||
},
|
||||
"queue-microtask": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.2.tgz",
|
||||
|
@ -3724,6 +3902,16 @@
|
|||
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz",
|
||||
"integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw=="
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz",
|
||||
"integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==",
|
||||
"requires": {
|
||||
"psl": "^1.1.33",
|
||||
"punycode": "^2.1.1",
|
||||
"universalify": "^0.1.2"
|
||||
}
|
||||
},
|
||||
"tree-kill": {
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
|
||||
|
@ -3930,6 +4118,11 @@
|
|||
"random-bytes": "~1.0.0"
|
||||
}
|
||||
},
|
||||
"universalify": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
|
||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
|
||||
},
|
||||
"uri-js": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
"@types/express": "^4.17.10",
|
||||
"@types/lodash": "^4.14.167",
|
||||
"@types/node": "^14.14.21",
|
||||
"@wulkanowy/sdk": "^0.1.1",
|
||||
"apollo-server-fastify": "^2.19.2",
|
||||
"dotenv": "^8.2.0",
|
||||
"fastify": "^3.10.1",
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Connection, createConnection, Repository } from 'typeorm';
|
||||
import type { Connection, Repository } from 'typeorm';
|
||||
import { createConnection } from 'typeorm';
|
||||
import Application from './entities/application';
|
||||
|
||||
class Database {
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import type { ObjectID } from 'typeorm';
|
||||
import {
|
||||
BaseEntity,
|
||||
Column, Entity, ObjectID, ObjectIdColumn,
|
||||
Column, Entity, ObjectIdColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
@Entity()
|
||||
|
|
|
@ -3,7 +3,7 @@ import { nanoid } from 'nanoid';
|
|||
import { scopes } from '../../constants';
|
||||
import database from '../../database/database';
|
||||
import { ParamError, ScopeError } from '../../errors';
|
||||
import { MyFastifyInstance } from '../../types';
|
||||
import type { MyFastifyInstance } from '../../types';
|
||||
import {
|
||||
getSessionData, isObject, parseScopeParam, validateOptionalParam, validateParam,
|
||||
} from '../../utils';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {
|
||||
import type {
|
||||
MyFastifyInstance,
|
||||
} from '../../types';
|
||||
import registerAuthorize from './authorize';
|
||||
|
|
15
backend/src/routes/website-api/errors.ts
Normal file
15
backend/src/routes/website-api/errors.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
export class UnknownPromptError extends Error {
|
||||
public name = 'UnknownPromptError';
|
||||
|
||||
public constructor() {
|
||||
super('Unknown prompt');
|
||||
}
|
||||
}
|
||||
|
||||
export class InvalidCredentialsError extends Error {
|
||||
public name = 'InvalidCredentialsError';
|
||||
|
||||
public constructor() {
|
||||
super('User with provided credentials not found');
|
||||
}
|
||||
}
|
|
@ -1,14 +1,18 @@
|
|||
import { ApolloServer } from 'apollo-server-fastify';
|
||||
import { buildSchema } from 'type-graphql';
|
||||
import { ApolloContext, MyFastifyInstance } from '../../types';
|
||||
import type { ApolloContext, MyFastifyInstance } from '../../types';
|
||||
import { getSessionData } from '../../utils';
|
||||
import LoginResolver from './resolvers/login-resolver';
|
||||
import PromptInfoResolver from './resolvers/prompt-info-resolver';
|
||||
import { WebsiteAPIContext } from './types';
|
||||
import type { WebsiteAPIContext } from './types';
|
||||
|
||||
export default async function registerWebsiteApi(server: MyFastifyInstance): Promise<void> {
|
||||
const schema = await buildSchema({
|
||||
authMode: 'error',
|
||||
resolvers: [PromptInfoResolver],
|
||||
resolvers: [
|
||||
PromptInfoResolver,
|
||||
LoginResolver,
|
||||
],
|
||||
});
|
||||
const apolloServer = new ApolloServer({
|
||||
schema,
|
||||
|
@ -19,7 +23,7 @@ export default async function registerWebsiteApi(server: MyFastifyInstance): Pro
|
|||
});
|
||||
await server.register(apolloServer.createHandler({
|
||||
cors: {
|
||||
origin: 'https://google.com',
|
||||
origin: false,
|
||||
},
|
||||
}));
|
||||
console.log(apolloServer.graphqlPath);
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import { Field, Int, ObjectType } from 'type-graphql';
|
||||
|
||||
@ObjectType()
|
||||
export default class LoginResultStudent {
|
||||
@Field(() => Int)
|
||||
public studentId!: number;
|
||||
|
||||
@Field(() => String)
|
||||
public name!: string;
|
||||
}
|
11
backend/src/routes/website-api/models/login-result.ts
Normal file
11
backend/src/routes/website-api/models/login-result.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { Field, ObjectType } from 'type-graphql';
|
||||
import LoginResultStudent from './login-result-student';
|
||||
|
||||
@ObjectType()
|
||||
export default class LoginResult {
|
||||
@Field(() => String)
|
||||
public encryptedPrivateKey!: string;
|
||||
|
||||
@Field(() => [LoginResultStudent])
|
||||
public students!: LoginResultStudent[];
|
||||
}
|
58
backend/src/routes/website-api/resolvers/login-resolver.ts
Normal file
58
backend/src/routes/website-api/resolvers/login-resolver.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
import { Client } from '@wulkanowy/sdk';
|
||||
import type { DiaryInfo } from '@wulkanowy/sdk/dist/diary/interfaces/diary/diary-info';
|
||||
import _ from 'lodash';
|
||||
import {
|
||||
Arg, Ctx, Mutation, Resolver,
|
||||
} from 'type-graphql';
|
||||
import {
|
||||
encryptSymmetrical, encryptWithPublicKey, generatePrivatePublicPair, requireEnvHex,
|
||||
} from '../../../utils';
|
||||
import { UnknownPromptError } from '../errors';
|
||||
import LoginResult from '../models/login-result';
|
||||
import type LoginResultStudent from '../models/login-result-student';
|
||||
import type { WebsiteAPIContext } from '../types';
|
||||
|
||||
@Resolver()
|
||||
export default class LoginResolver {
|
||||
@Mutation(() => LoginResult)
|
||||
public async login(
|
||||
@Arg('promptId') promptId: string,
|
||||
@Arg('username') username: string,
|
||||
@Arg('password') password: string,
|
||||
@Arg('host') host: string,
|
||||
@Ctx() { sessionData }: WebsiteAPIContext,
|
||||
): Promise<LoginResult> {
|
||||
const prompt = sessionData.prompts.get(promptId);
|
||||
if (!prompt) throw new UnknownPromptError();
|
||||
const client = new Client(host, () => ({
|
||||
username,
|
||||
password,
|
||||
}));
|
||||
await client.login();
|
||||
const diaryList = await client.getDiaryList();
|
||||
const { privateKey, publicKey } = await generatePrivatePublicPair();
|
||||
const encryptedPrivateKey = encryptSymmetrical(
|
||||
privateKey,
|
||||
requireEnvHex('CREDENTIALS_PRIVATE_KEY_ENCRYPT_KEY'),
|
||||
);
|
||||
const encryptedPassword = encryptWithPublicKey(password, publicKey);
|
||||
console.log(diaryList.map((e) => e.serialized.info));
|
||||
const students = _.toPairs(_.groupBy(diaryList.map((e) => e.serialized.info), 'studentId'))
|
||||
.map(([, diaryInfoList]: [string, DiaryInfo[]]) => diaryInfoList[0])
|
||||
.map<LoginResultStudent>((diaryInfo) => ({
|
||||
name: `${diaryInfo.studentFirstName} ${diaryInfo.studentSurname}`,
|
||||
studentId: diaryInfo.studentId,
|
||||
}));
|
||||
prompt.loginInfo = {
|
||||
encryptedPassword,
|
||||
host,
|
||||
username,
|
||||
availableStudentIds: students.map(({ studentId }) => studentId),
|
||||
};
|
||||
return {
|
||||
encryptedPrivateKey,
|
||||
students,
|
||||
};
|
||||
}
|
||||
}
|
|
@ -1,23 +1,25 @@
|
|||
/* eslint-disable class-methods-use-this */
|
||||
import type { ResolverInterface } from 'type-graphql';
|
||||
import {
|
||||
Arg, Ctx, FieldResolver, Query, Resolver, ResolverInterface, Root,
|
||||
Arg, Ctx, FieldResolver, Query, Resolver, Root,
|
||||
} from 'type-graphql';
|
||||
import database from '../../../database/database';
|
||||
import { UnknownPromptError } from '../errors';
|
||||
import PromptInfo from '../models/prompt-info';
|
||||
import PromptInfoApplication from '../models/prompt-info-application';
|
||||
import { WebsiteAPIContext } from '../types';
|
||||
import type PromptInfoApplication from '../models/prompt-info-application';
|
||||
import type { WebsiteAPIContext } from '../types';
|
||||
|
||||
@Resolver(PromptInfo)
|
||||
export default class PromptInfoResolver implements ResolverInterface<PromptInfo> {
|
||||
@Query(() => PromptInfo)
|
||||
public promptInfo(
|
||||
@Arg('id') id: string,
|
||||
@Arg('promptId') promptId: string,
|
||||
@Ctx() { sessionData }: WebsiteAPIContext,
|
||||
): Partial<PromptInfo> {
|
||||
const prompt = sessionData.prompts.get(id);
|
||||
if (!prompt) throw new Error('Prompt data not found');
|
||||
const prompt = sessionData.prompts.get(promptId);
|
||||
if (!prompt) throw new UnknownPromptError();
|
||||
return {
|
||||
id,
|
||||
id: promptId,
|
||||
clientId: prompt.clientId,
|
||||
scopes: prompt.scopes,
|
||||
};
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { ApolloContext, SessionData } from '../../types';
|
||||
import type SessionData from '../../session-data';
|
||||
import type { ApolloContext } from '../../types';
|
||||
|
||||
export interface WebsiteAPIContext extends ApolloContext {
|
||||
sessionData: SessionData;
|
||||
|
|
5
backend/src/session-data.ts
Normal file
5
backend/src/session-data.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import type { Prompt } from './types';
|
||||
|
||||
export default class SessionData {
|
||||
public prompts = new Map<string, Prompt>();
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
import type {
|
||||
FastifyInstance, FastifyRequest, RawReplyDefaultExpression, RawRequestDefaultExpression, RawServerDefault,
|
||||
} from 'fastify';
|
||||
import type SessionData from './session-data';
|
||||
|
||||
export interface Prompt {
|
||||
clientId: string;
|
||||
|
@ -11,10 +12,12 @@ export interface Prompt {
|
|||
value: string;
|
||||
method: 'plain' | 'S256';
|
||||
};
|
||||
}
|
||||
|
||||
export interface SessionData {
|
||||
prompts: Map<string, Prompt>
|
||||
loginInfo?: {
|
||||
host: string;
|
||||
username: string;
|
||||
encryptedPassword: string;
|
||||
availableStudentIds: number[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
|
|
43
backend/src/utils/crypto.ts
Normal file
43
backend/src/utils/crypto.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import * as crypto from 'crypto';
|
||||
import * as util from 'util';
|
||||
|
||||
export function generatePrivatePublicPair(): Promise<{
|
||||
privateKey: string;
|
||||
publicKey: string;
|
||||
}> {
|
||||
return util.promisify(crypto.generateKeyPair)('rsa', {
|
||||
modulusLength: 1024,
|
||||
publicKeyEncoding: {
|
||||
type: 'spki',
|
||||
format: 'pem',
|
||||
},
|
||||
privateKeyEncoding: {
|
||||
type: 'pkcs8',
|
||||
format: 'pem',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function encryptSymmetrical(value: string, key: Buffer): string {
|
||||
const ivBuffer = crypto.randomBytes(16);
|
||||
const cipher = crypto.createCipheriv('aes-256-cbc', key, ivBuffer);
|
||||
const encrypted = Buffer.concat([cipher.update(value), cipher.final()]);
|
||||
return `${encrypted.toString('base64')}@${ivBuffer.toString('base64')}`;
|
||||
}
|
||||
|
||||
export function decryptSymmetrical(encrypted: string, key: Buffer): string {
|
||||
const [iv, encryptedData] = encrypted.split('@');
|
||||
const ivBuffer = Buffer.from(iv, 'base64');
|
||||
const encryptedBuffer = Buffer.from(encryptedData, 'base64');
|
||||
const decipher = crypto.createDecipheriv('aes-256-cbc', key, ivBuffer);
|
||||
const decrypted = Buffer.concat([decipher.update(encryptedBuffer), decipher.final()]);
|
||||
return decrypted.toString();
|
||||
}
|
||||
|
||||
export function encryptWithPublicKey(value: string, publicKey: string): string {
|
||||
return crypto.publicEncrypt(publicKey, Buffer.from(value)).toString('base64');
|
||||
}
|
||||
|
||||
export function decryptWithPrivateKey(encrypted: string, privateKey: string): string {
|
||||
return crypto.privateDecrypt(privateKey, Buffer.from(encrypted, 'base64')).toString();
|
||||
}
|
|
@ -1,6 +1,9 @@
|
|||
import _ from 'lodash';
|
||||
import { ParamError } from './errors';
|
||||
import { Prompt, Session, SessionData } from './types';
|
||||
import { ParamError } from '../errors';
|
||||
import SessionData from '../session-data';
|
||||
import type { Session } from '../types';
|
||||
|
||||
export * from './crypto';
|
||||
|
||||
export function requireEnv(name: string): string {
|
||||
const value = process.env[name];
|
||||
|
@ -8,6 +11,10 @@ export function requireEnv(name: string): string {
|
|||
return value;
|
||||
}
|
||||
|
||||
export function requireEnvHex(name: string): Buffer {
|
||||
return Buffer.from(requireEnv(name), 'hex');
|
||||
}
|
||||
|
||||
export function parseIntStrict(value: string, radix = 10): number {
|
||||
const number = parseInt(value, radix);
|
||||
if (_.isNaN(number)) throw new Error(`Cannot parse ${value} to int`);
|
||||
|
@ -47,9 +54,7 @@ export function parseScopeParam(key: string, value: unknown): string[] {
|
|||
export function getSessionData(session: Session): SessionData {
|
||||
if (session.data === undefined) {
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
session.data = {
|
||||
prompts: new Map<string, Prompt>(),
|
||||
};
|
||||
session.data = new SessionData();
|
||||
}
|
||||
return session.data;
|
||||
}
|
Loading…
Reference in a new issue