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", "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" "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": { "@sqltools/formatter": {
"version": "1.2.2", "version": "1.2.2",
"resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz", "resolved": "https://registry.npmjs.org/@sqltools/formatter/-/formatter-1.2.2.tgz",
"integrity": "sha512-/5O7Fq6Vnv8L6ucmPjaWbVG1XkP4FO+w5glqfkIsq3Xw4oyNAdJddbnYodNDAfjVUvo/rrSCTom4kAND7T1o5Q==" "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": { "@types/accepts": {
"version": "1.3.5", "version": "1.3.5",
"resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz", "resolved": "https://registry.npmjs.org/@types/accepts/-/accepts-1.3.5.tgz",
@ -234,6 +247,17 @@
"@types/node": "*" "@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": { "@types/connect": {
"version": "3.4.34", "version": "3.4.34",
"resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.34.tgz", "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", "resolved": "https://registry.npmjs.org/@types/http-assert/-/http-assert-1.5.1.tgz",
"integrity": "sha512-PGAK759pxyfXE78NbKxyfRcWYA/KwW17X290cNev/qAsn9eQIxkH4shoNBafH37wewhDG/0p1cHPbK6+SzZjWQ==" "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": { "@types/http-errors": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-1.8.0.tgz", "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", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.2.tgz",
"integrity": "sha512-GJhpTepz2udxGexqos8wgaBx4I/zWIDPh/KOGEwAqtuGDkOUJu5eFvwmdBX4AmB8Odsr+9pHCQqiAqDL/yKMKw==" "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": { "@types/koa": {
"version": "2.11.6", "version": "2.11.6",
"resolved": "https://registry.npmjs.org/@types/koa/-/koa-2.11.6.tgz", "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", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.3.tgz",
"integrity": "sha512-ewFXqrQHlFsgc09MK5jP5iR7vumV/BYayNC6PgJO2LPe8vrnNFyjQjSppfEngITi0qvfKtzFvgKymGheFM9UOA==" "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": { "@types/semver": {
"version": "7.3.4", "version": "7.3.4",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.4.tgz",
@ -1126,6 +1171,25 @@
"dicer": "0.3.0" "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": { "call-bind": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
@ -1272,6 +1336,14 @@
"wrap-ansi": "^7.0.0" "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": { "color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@ -1417,6 +1489,21 @@
"integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
"dev": true "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": { "deep-is": {
"version": "0.1.3", "version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "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", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz",
"integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==" "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": { "define-properties": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
@ -2292,6 +2384,14 @@
"integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=", "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=",
"dev": true "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": { "glob": {
"version": "7.1.6", "version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@ -2337,6 +2437,24 @@
"slash": "^3.0.0" "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": { "graceful-fs": {
"version": "4.2.4", "version": "4.2.4",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
@ -2464,6 +2582,11 @@
"entities": "^2.0.0" "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": { "http-errors": {
"version": "1.8.0", "version": "1.8.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.0.tgz", "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": { "ieee754": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -2683,6 +2815,11 @@
"esprima": "^4.0.0" "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": { "json-schema-traverse": {
"version": "0.4.1", "version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@ -2703,6 +2840,14 @@
"minimist": "^1.2.0" "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": { "leven": {
"version": "2.1.0", "version": "2.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
@ -2787,6 +2932,11 @@
"signal-exit": "^3.0.0" "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": { "lru-cache": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
@ -2939,6 +3089,11 @@
"mime-db": "1.45.0" "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": { "minimatch": {
"version": "3.0.4", "version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
@ -3039,6 +3194,11 @@
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true "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": { "nth-check": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.0.0.tgz",
@ -3134,6 +3294,11 @@
"word-wrap": "^1.2.3" "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": { "p-limit": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "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", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.1.tgz",
"integrity": "sha512-RyYpQ6Q5/drsJyOhrWHYMWTedvjTIat+FTwv0K4yoUxzvekw2aRHMQJLlnvt8UantkZg2++bEzD9EdxXqkWf4A==" "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": { "random-bytes": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
@ -3497,12 +3667,25 @@
"path-parse": "^1.0.6" "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": { "resolve-from": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
"dev": true "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": { "ret": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz", "resolved": "https://registry.npmjs.org/ret/-/ret-0.2.2.tgz",

View file

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

View file

@ -1,10 +1,18 @@
import { ApolloError } from 'apollo-server-fastify'; 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 name = 'UnknownPromptError';
public constructor() { public constructor() {
super('Unknown prompt'); super('Unknown prompt', 'UNKNOWN_PROMPT_ERROR');
} }
} }

View file

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

View file

@ -1,3 +1,4 @@
import got from 'got';
import _ from 'lodash'; import _ from 'lodash';
import { ParamError } from '../errors'; import { ParamError } from '../errors';
import SessionData from '../session-data'; import SessionData from '../session-data';
@ -58,3 +59,20 @@ export function getSessionData(session: Session): SessionData {
} }
return session.data; 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: { rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
'import/prefer-default-export': ['warn']
}, },
overrides: [ overrides: [
{ {

View file

@ -16083,6 +16083,11 @@
"resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz", "resolved": "https://registry.npmjs.org/vue-property-decorator/-/vue-property-decorator-9.1.2.tgz",
"integrity": "sha512-xYA8MkZynPBGd/w5QFJ2d/NM0z/YeegMqYTphy7NJQXbZcuU6FC6AOdUAcy4SXP+YnkerC6AfH+ldg7PDk9ESQ==" "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": { "vue-style-loader": {
"version": "4.1.2", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/vue-style-loader/-/vue-style-loader-4.1.2.tgz", "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": "^2.6.11",
"vue-class-component": "^7.2.3", "vue-class-component": "^7.2.3",
"vue-property-decorator": "^9.1.2", "vue-property-decorator": "^9.1.2",
"vue-recaptcha": "^1.3.0",
"vuetify": "^2.2.11" "vuetify": "^2.2.11"
}, },
"devDependencies": { "devDependencies": {

View file

@ -8,6 +8,7 @@
<title><%= htmlWebpackPlugin.options.title %></title> <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://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"> <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> </head>
<body> <body>
<noscript> <noscript>

View file

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

View file

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

View file

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