Add captcha

This commit is contained in:
Dominik Korsa 2021-01-21 16:39:46 +01:00
parent 8c87c01810
commit ec1252554c
No known key found for this signature in database
GPG key ID: 546F986F71A6FE6E
14 changed files with 287 additions and 16 deletions

View file

@ -212,11 +212,24 @@
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
},
"@sindresorhus/is": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.0.tgz",
"integrity": "sha512-FyD2meJpDPjyNQejSjvnhpgI/azsQkA4lGbuu5BQZfjvJ9cbRZXzeWL2HceCekW4lixO9JPesIIQkSoLjeJHNQ=="
},
"@sqltools/formatter": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz",
"integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q=="
},
"@szmarczak/http-timer": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.5.tgz",
"integrity": "sha512-PyRA9sm1Yayuj5OIoJ1hGt2YISX45w9WcFbh6ddT0Z/0yaFxOtGLInr4jUfU1EAFVs0Yfyfev4RNwBlUaHdlDQ==",
"requires": {
"defer-to-connect": "^2.0.0"
}
},
"@types/accepts": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
@ -234,6 +247,17 @@
"@types/node": "*"
}
},
"@types/cacheable-request": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.1.tgz",
"integrity": "sha512-ykFq2zmBGOCbpIXtoVbz4SKY5QriWPh3AjyU4G74RYbtt5yOc5OfaY75ftjg7mikMOla1CTGpX3lLbuJh8DTrQ==",
"requires": {
"@types/http-cache-semantics": "*",
"@types/keyv": "*",
"@types/node": "*",
"@types/responselike": "*"
}
},
"@types/connect": {
"version": "3.4.34",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz",
@ -312,6 +336,11 @@
"resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz",
"integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ=="
},
"@types/http-cache-semantics": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.0.tgz",
"integrity": "sha512-c3Xy026kOF7QOTn00hbIllV1dLR9hG9NkSrLQgCVs8NF6sBU+VGWjD3wLPhmh1TYAc7ugCFsvHYMN4VcBN1U1A=="
},
"@types/http-errors": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz",
@ -334,6 +363,14 @@
"resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz",
"integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw=="
},
"@types/keyv": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.1.tgz",
"integrity": "sha512-MPtoySlAZQ37VoLaPcTHCu1RWJ4llDkULYZIzOYxlhxBqYPB0RsRlmMU0R6tahtFe27mIdkHV+551ZWV4PLmVw==",
"requires": {
"@types/node": "*"
}
},
"@types/koa": {
"version": "2.11.6",
"resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.6.tgz",
@ -401,6 +438,14 @@
"resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA=="
},
"@types/responselike": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz",
"integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==",
"requires": {
"@types/node": "*"
}
},
"@types/semver": {
"version": "7.3.4",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz",
@ -1126,6 +1171,25 @@
"dicer": "0.3.0"
}
},
"cacheable-lookup": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz",
"integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA=="
},
"cacheable-request": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.1.tgz",
"integrity": "sha512-lt0mJ6YAnsrBErpTMWeu5kl/tg9xMAWjavYTN6VQXM1A/teBITuNcccXsCxF0tDQQJf9DfAaX5O4e0zp0KlfZw==",
"requires": {
"clone-response": "^1.0.2",
"get-stream": "^5.1.0",
"http-cache-semantics": "^4.0.0",
"keyv": "^4.0.0",
"lowercase-keys": "^2.0.0",
"normalize-url": "^4.1.0",
"responselike": "^2.0.0"
}
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@ -1272,6 +1336,14 @@
"wrap-ansi": "^7.0.0"
}
},
"clone-response": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz",
"integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=",
"requires": {
"mimic-response": "^1.0.0"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -1417,6 +1489,21 @@
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true
},
"decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"requires": {
"mimic-response": "^3.1.0"
},
"dependencies": {
"mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
}
}
},
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
@ -1428,6 +1515,11 @@
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg=="
},
"defer-to-connect": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.0.tgz",
"integrity": "sha512-bYL2d05vOSf1JEZNx5vSAtPuBMkX8K9EUutg7zlKvTqKXHt7RhWJFbmd7qakVuf13i+IkGmp6FwSsONOf6VYIg=="
},
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@ -2292,6 +2384,14 @@
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
"dev": true
},
"get-stream": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
"integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
"requires": {
"pump": "^3.0.0"
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@ -2337,6 +2437,24 @@
"slash": "^3.0.0"
}
},
"got": {
"version": "11.8.1",
"resolved": "https://registry.npmjs.org/got/-/got-11.8.1.tgz",
"integrity": "sha512-9aYdZL+6nHmvJwHALLwKSUZ0hMwGaJGYv3hoPLPgnT8BoBXm1SjnZeky+91tfwJaDzun2s4RsBRy48IEYv2q2Q==",
"requires": {
"@sindresorhus/is": "^4.0.0",
"@szmarczak/http-timer": "^4.0.5",
"@types/cacheable-request": "^6.0.1",
"@types/responselike": "^1.0.0",
"cacheable-lookup": "^5.0.3",
"cacheable-request": "^7.0.1",
"decompress-response": "^6.0.0",
"http2-wrapper": "^1.0.0-beta.5.2",
"lowercase-keys": "^2.0.0",
"p-cancelable": "^2.0.0",
"responselike": "^2.0.0"
}
},
"graceful-fs": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@ -2464,6 +2582,11 @@
"entities": "^2.0.0"
}
},
"http-cache-semantics": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.0.tgz",
"integrity": "sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ=="
},
"http-errors": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz",
@ -2483,6 +2606,15 @@
}
}
},
"http2-wrapper": {
"version": "1.0.0-beta.5.2",
"resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.0-beta.5.2.tgz",
"integrity": "sha512-xYz9goEyBnC8XwXDTuC/MZ6t+MrKVQZOk4s7+PaDkwIsQd8IwqvM+0M6bA/2lvG8GHXcPdf+MejTUeO2LCPCeQ==",
"requires": {
"quick-lru": "^5.1.1",
"resolve-alpn": "^1.0.0"
}
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -2683,6 +2815,11 @@
"esprima": "^4.0.0"
}
},
"json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
"integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@ -2703,6 +2840,14 @@
"minimist": "^1.2.0"
}
},
"keyv": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.3.tgz",
"integrity": "sha512-zdGa2TOpSZPq5mU6iowDARnMBZgtCqJ11dJROFi6tg6kTn4nuUdU09lFyLFSaHrWqpIJ+EBq4E8/Dc0Vx5vLdA==",
"requires": {
"json-buffer": "3.0.1"
}
},
"leven": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
@ -2787,6 +2932,11 @@
"signal-exit": "^3.0.0"
}
},
"lowercase-keys": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz",
"integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA=="
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -2939,6 +3089,11 @@
"mime-db": "1.45.0"
}
},
"mimic-response": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz",
"integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ=="
},
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@ -3039,6 +3194,11 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true
},
"normalize-url": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz",
"integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ=="
},
"nth-check": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
@ -3134,6 +3294,11 @@
"word-wrap": "^1.2.3"
}
},
"p-cancelable": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.0.0.tgz",
"integrity": "sha512-wvPXDmbMmu2ksjkB4Z3nZWTSkJEb9lqVdMaCKpZUGJG9TMiNp9XcbG3fn9fPKjem04fJMJnXoyFPk2FmgiaiNg=="
},
"p-limit": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
@ -3369,6 +3534,11 @@
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz",
"integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A=="
},
"quick-lru": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz",
"integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="
},
"random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
@ -3497,12 +3667,25 @@
"path-parse": "^1.0.6"
}
},
"resolve-alpn": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.0.0.tgz",
"integrity": "sha512-rTuiIEqFmGxne4IovivKSDzld2lWW9QCjqv80SYjPgf+gS35eaCAjaP54CCwGAwBtnCsvNLYtqxe1Nw+i6JEmA=="
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true
},
"responselike": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz",
"integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==",
"requires": {
"lowercase-keys": "^2.0.0"
}
},
"ret": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz",

View file

@ -26,6 +26,7 @@
"fastify-http-proxy": "^4.2.0",
"fastify-sensible": "^3.1.0",
"fastify-session": "^5.2.1",
"got": "^11.8.1",
"lodash": "^4.17.20",
"mongodb": "^3.6.3",
"nanoid": "^3.1.20",

View file

@ -1,10 +1,18 @@
import { ApolloError } from 'apollo-server-fastify';
export class UnknownPromptError extends Error {
export class CaptchaError extends ApolloError {
public name = 'CaptchaError';
public constructor() {
super('Captcha validation failed', 'CAPTCHA_ERROR');
}
}
export class UnknownPromptError extends ApolloError {
public name = 'UnknownPromptError';
public constructor() {
super('Unknown prompt');
super('Unknown prompt', 'UNKNOWN_PROMPT_ERROR');
}
}

View file

@ -6,9 +6,9 @@ import {
Arg, Ctx, Mutation, Resolver,
} from 'type-graphql';
import {
encryptSymmetrical, encryptWithPublicKey, generatePrivatePublicPair, isObject,
encryptSymmetrical, encryptWithPublicKey, generatePrivatePublicPair, isObject, verifyCaptchaResponse,
} from '../../../utils';
import { InvalidVulcanCredentialsError, UnknownPromptError } from '../errors';
import { CaptchaError, InvalidVulcanCredentialsError, UnknownPromptError } from '../errors';
import LoginResult from '../models/login-result';
import type LoginResultStudent from '../models/login-result-student';
import type { WebsiteAPIContext } from '../types';
@ -21,10 +21,12 @@ export default class LoginResolver {
@Arg('username') username: string,
@Arg('password') password: string,
@Arg('host') host: string,
@Arg('captchaResponse') captchaResponse: string,
@Ctx() { sessionData, reply }: WebsiteAPIContext,
): Promise<LoginResult> {
const prompt = sessionData.prompts.get(promptId);
if (!prompt) throw new UnknownPromptError();
if (!await verifyCaptchaResponse(captchaResponse)) throw new CaptchaError();
const client = new Client(host, () => ({
username,
password,

View file

@ -1,3 +1,4 @@
import got from 'got';
import _ from 'lodash';
import { ParamError } from '../errors';
import SessionData from '../session-data';
@ -58,3 +59,20 @@ export function getSessionData(session: Session): SessionData {
}
return session.data;
}
export async function verifyCaptchaResponse(response: string): Promise<boolean> {
const { body } = await got.post<{
success: boolean,
challenge_ts?: string;
hostname?: string;
'error-codes'?: string[]
}>('https://www.google.com/recaptcha/api/siteverify', {
responseType: 'json',
searchParams: {
secret: requireEnv('CAPTCHA_SECRET'),
response,
},
});
console.log(body);
return body.success;
}

1
website/.env.development Normal file
View file

@ -0,0 +1 @@
VUE_APP_CAPTCHA_SITE_KEY=6LfAxzUaAAAAANF5VLy39hbgx5K6WTQTa2YDdhmC

View file

@ -14,6 +14,7 @@ module.exports = {
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'import/prefer-default-export': ['warn']
},
overrides: [
{

View file

@ -16083,6 +16083,11 @@
"resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz",
"integrity": "sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ=="
},
"vue-recaptcha": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/vue-recaptcha/-/vue-recaptcha-1.3.0.tgz",
"integrity": "sha512-9Qf1niyHq4QbEUhsvdUkS8BoOyhYwpp8v+imUSj67ffDo9RQ6h8Ekq8EGnw/GKViXCwWalp7EEY/n/fOtU0FyA=="
},
"vue-style-loader": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz",

View file

@ -16,6 +16,7 @@
"vue": "^2.6.11",
"vue-class-component": "^7.2.3",
"vue-property-decorator": "^9.1.2",
"vue-recaptcha": "^1.3.0",
"vuetify": "^2.2.11"
},
"devDependencies": {

View file

@ -8,6 +8,7 @@
<title><%= htmlWebpackPlugin.options.title %></title>
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css">
<script src="https://www.google.com/recaptcha/api.js?onload=vueRecaptchaApiLoaded&render=explicit" async defer></script>
</head>
<body>
<noscript>

View file

@ -17,19 +17,31 @@
outlined
:rules="requiredRules"
/>
<div class="overflow-x-auto">
<vue-recaptcha
ref="recaptcha"
class="d-inline-block"
:sitekey="captchaSiteKey"
@verify="captchaVerify"
@expired="captchaReset()"
@error="captchaReset()"
/>
</div>
</div>
<v-alert type="error" class="mx-2" :value="error === 'invalid-credentials'">
Dane logowania nieprawidłowe
</v-alert>
<v-alert type="error" class="mx-2" :value="error === 'other'">
Podczas logowania wystąpił błąd
<v-alert type="error" class="mx-2" :value="errorMessage !== null">
{{ errorMessage }}
</v-alert>
<v-card-actions>
<v-btn color="primary" text outlined @click="back" :disabled="loading">
Wróć
</v-btn>
<v-spacer />
<v-btn color="primary" :loading="loading" type="submit" :disabled="!formValid">
<v-btn
color="primary"
:loading="loading"
type="submit"
:disabled="!formValid || !this.captchaResponse"
>
Zaloguj się
</v-btn>
</v-card-actions>
@ -44,6 +56,8 @@ import {
import { PromptInfo } from '@/types';
import { hasErrorCode, sdk } from '@/pages/authenticate-prompt/sdk';
import { InputValidationRules } from 'vuetify';
import VueRecaptcha from 'vue-recaptcha';
import { requireEnv } from '@/utils';
interface VForm extends HTMLFormElement {
validate(): boolean;
@ -52,6 +66,9 @@ interface VForm extends HTMLFormElement {
@Component({
name: 'LoginWindow',
components: {
VueRecaptcha,
},
})
export default class LoginWindow extends Vue {
@Prop({
@ -62,6 +79,10 @@ export default class LoginWindow extends Vue {
@Ref('form') form!: VForm;
@Ref('recaptcha') recaptcha!: VueRecaptcha;
readonly captchaSiteKey = requireEnv('VUE_APP_CAPTCHA_SITE_KEY');
readonly hosts = [
{
text: 'Vulcan',
@ -85,19 +106,30 @@ export default class LoginWindow extends Vue {
password = '';
captchaResponse: string | null = null;
loading = false;
error: 'invalid-credentials' | 'other' | null = null;
error: 'invalid-credentials' | 'other' | 'captcha' | null = null;
reset() {
this.host = 'fakelog.cf';
this.username = '';
this.password = '';
this.form.resetValidation();
this.recaptcha.reset();
}
captchaVerify(response: string) {
this.captchaResponse = response;
}
captchaReset() {
this.captchaResponse = null;
}
async submit() {
if (this.loading || !this.formValid) return;
if (this.loading || !this.formValid || !this.captchaResponse) return;
this.error = null;
this.loading = true;
try {
@ -106,13 +138,16 @@ export default class LoginWindow extends Vue {
host: this.host,
username: this.username,
password: this.password,
captchaResponse: this.captchaResponse,
});
const { students } = login;
this.$emit('login', { students });
this.reset();
} catch (error) {
console.error(error);
this.error = hasErrorCode(error, 'INVALID_VULCAN_CREDENTIALS') ? 'invalid-credentials' : 'other';
if (hasErrorCode(error, 'INVALID_VULCAN_CREDENTIALS')) this.error = 'invalid-credentials';
if (hasErrorCode(error, 'CAPTCHA_ERROR')) this.error = 'captcha';
else this.error = 'other';
}
this.loading = false;
}
@ -125,5 +160,12 @@ export default class LoginWindow extends Vue {
mounted() {
this.reset();
}
get errorMessage() {
if (this.error === null) return null;
if (this.error === 'invalid-credentials') return 'Dane logowania są nieprawidłowe';
if (this.error === 'captcha') return 'Błąd weryfikacji';
return 'Podczas logowania wystąpił błąd';
}
}
</script>

View file

@ -53,6 +53,7 @@ export type Mutation = {
};
export type MutationLoginArgs = {
captchaResponse: Scalars['String'];
host: Scalars['String'];
password: Scalars['String'];
username: Scalars['String'];
@ -75,6 +76,7 @@ export type LoginMutationVariables = Exact<{
host: Scalars['String'];
username: Scalars['String'];
password: Scalars['String'];
captchaResponse: Scalars['String'];
}>;
export type LoginMutation = (
@ -105,12 +107,13 @@ export type GetPromptInfoQuery = (
);
export const LoginDocument = gql`
mutation Login($promptId: String!, $host: String!, $username: String!, $password: String!) {
mutation Login($promptId: String!, $host: String!, $username: String!, $password: String!, $captchaResponse: String!) {
login(
host: $host
password: $password
username: $username
promptId: $promptId
captchaResponse: $captchaResponse
) {
students {
studentId

View file

@ -1,7 +1,7 @@
import gql from 'graphql-tag';
export default gql`mutation Login($promptId: String!, $host: String!, $username: String!, $password: String!) {
login(host: $host, password: $password, username: $username, promptId: $promptId) {
export default gql`mutation Login($promptId: String!, $host: String!, $username: String!, $password: String!, $captchaResponse: String!) {
login(host: $host, password: $password, username: $username, promptId: $promptId, captchaResponse: $captchaResponse) {
students {
studentId
name

5
website/src/utils.ts Normal file
View file

@ -0,0 +1,5 @@
export function requireEnv(name: string): string {
const value = process.env[name];
if (value === undefined) throw new Error(`Environment variable ${name} not set`);
return value;
}