mirror of
https://github.com/mCaptcha/mCaptcha.git
synced 2025-02-13 23:19:46 +03:00
swagger ui
This commit is contained in:
parent
8cc3146389
commit
a3a118927e
25 changed files with 962 additions and 22 deletions
Cargo.lockCargo.toml
docs
favicon-16x16.pngfavicon-32x32.pngindex.htmloauth2-redirect.htmlswagger-ui-bundle.jsswagger-ui-bundle.js.mapswagger-ui-es-bundle-core.jsswagger-ui-es-bundle-core.js.mapswagger-ui-es-bundle.jsswagger-ui-es-bundle.js.mapswagger-ui-standalone-preset.jsswagger-ui-standalone-preset.js.mapswagger-ui.cssswagger-ui.css.mapswagger-ui.jsswagger-ui.js.mapswagger.json
openapi.yamlsrc
55
Cargo.lock
generated
55
Cargo.lock
generated
|
@ -1222,8 +1222,10 @@ dependencies = [
|
|||
"lazy_static",
|
||||
"log",
|
||||
"m_captcha",
|
||||
"mime_guess",
|
||||
"pretty_env_logger",
|
||||
"rand 0.8.3",
|
||||
"rust-embed",
|
||||
"sailfish",
|
||||
"serde 1.0.125",
|
||||
"serde_json",
|
||||
|
@ -2206,6 +2208,39 @@ dependencies = [
|
|||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed"
|
||||
version = "5.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2fe1fe6aac5d6bb9e1ffd81002340363272a7648234ec7bdfac5ee202cb65523"
|
||||
dependencies = [
|
||||
"rust-embed-impl",
|
||||
"rust-embed-utils",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-impl"
|
||||
version = "5.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3ed91c41c42ef7bf687384439c312e75e0da9c149b0390889b94de3c7d9d9e66"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rust-embed-utils",
|
||||
"syn",
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-embed-utils"
|
||||
version = "5.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a512219132473ab0a77b52077059f1c47ce4af7fbdc94503e9862a34422876d"
|
||||
dependencies = [
|
||||
"walkdir",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rust-ini"
|
||||
version = "0.13.0"
|
||||
|
@ -2277,6 +2312,15 @@ dependencies = [
|
|||
"sailfish-compiler",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "same-file"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
|
||||
dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
@ -3151,6 +3195,17 @@ version = "0.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "walkdir"
|
||||
version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
|
||||
dependencies = [
|
||||
"same-file",
|
||||
"winapi 0.3.9",
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
|
|
|
@ -28,6 +28,8 @@ actix-identity = "0.3"
|
|||
actix-http = "2.2"
|
||||
actix-rt = "1"
|
||||
actix-files = "0.4"
|
||||
mime_guess = "2.0.3"
|
||||
rust-embed = "5.9.0"
|
||||
|
||||
futures = "0.3"
|
||||
|
||||
|
|
BIN
docs/favicon-16x16.png
Normal file
BIN
docs/favicon-16x16.png
Normal file
Binary file not shown.
After (image error) Size: 665 B |
BIN
docs/favicon-32x32.png
Normal file
BIN
docs/favicon-32x32.png
Normal file
Binary file not shown.
After (image error) Size: 628 B |
60
docs/index.html
Normal file
60
docs/index.html
Normal file
|
@ -0,0 +1,60 @@
|
|||
<!-- HTML for static distribution bundle build -->
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Swagger UI</title>
|
||||
<link rel="stylesheet" type="text/css" href="./swagger-ui.css" />
|
||||
<link rel="icon" type="image/png" href="./favicon-32x32.png" sizes="32x32" />
|
||||
<link rel="icon" type="image/png" href="./favicon-16x16.png" sizes="16x16" />
|
||||
<style>
|
||||
html
|
||||
{
|
||||
box-sizing: border-box;
|
||||
overflow: -moz-scrollbars-vertical;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
|
||||
*,
|
||||
*:before,
|
||||
*:after
|
||||
{
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
body
|
||||
{
|
||||
margin:0;
|
||||
background: #fafafa;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
|
||||
<script src="./swagger-ui-bundle.js" charset="UTF-8"> </script>
|
||||
<script src="./swagger-ui-standalone-preset.js" charset="UTF-8"> </script>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
// Begin Swagger UI call region
|
||||
const ui = SwaggerUIBundle({
|
||||
url: "/docs/swagger.json",
|
||||
dom_id: '#swagger-ui',
|
||||
deepLinking: true,
|
||||
presets: [
|
||||
SwaggerUIBundle.presets.apis,
|
||||
SwaggerUIStandalonePreset
|
||||
],
|
||||
plugins: [
|
||||
SwaggerUIBundle.plugins.DownloadUrl
|
||||
],
|
||||
layout: "StandaloneLayout"
|
||||
});
|
||||
// End Swagger UI call region
|
||||
|
||||
window.ui = ui;
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
75
docs/oauth2-redirect.html
Normal file
75
docs/oauth2-redirect.html
Normal file
|
@ -0,0 +1,75 @@
|
|||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<head>
|
||||
<title>Swagger UI: OAuth2 Redirect</title>
|
||||
</head>
|
||||
<body>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&");
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';});
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value);
|
||||
}
|
||||
) : {};
|
||||
|
||||
isValid = qp.state === sentState;
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode" ||
|
||||
oauth2.auth.schema.get("flow") === "authorization_code"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg;
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
|
||||
window.addEventListener('DOMContentLoaded', function () {
|
||||
run();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
3
docs/swagger-ui-bundle.js
Normal file
3
docs/swagger-ui-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/swagger-ui-bundle.js.map
Normal file
1
docs/swagger-ui-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
3
docs/swagger-ui-es-bundle-core.js
Normal file
3
docs/swagger-ui-es-bundle-core.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/swagger-ui-es-bundle-core.js.map
Normal file
1
docs/swagger-ui-es-bundle-core.js.map
Normal file
File diff suppressed because one or more lines are too long
3
docs/swagger-ui-es-bundle.js
Normal file
3
docs/swagger-ui-es-bundle.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/swagger-ui-es-bundle.js.map
Normal file
1
docs/swagger-ui-es-bundle.js.map
Normal file
File diff suppressed because one or more lines are too long
3
docs/swagger-ui-standalone-preset.js
Normal file
3
docs/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/swagger-ui-standalone-preset.js.map
Normal file
1
docs/swagger-ui-standalone-preset.js.map
Normal file
File diff suppressed because one or more lines are too long
4
docs/swagger-ui.css
Normal file
4
docs/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
1
docs/swagger-ui.css.map
Normal file
1
docs/swagger-ui.css.map
Normal file
File diff suppressed because one or more lines are too long
3
docs/swagger-ui.js
Normal file
3
docs/swagger-ui.js
Normal file
File diff suppressed because one or more lines are too long
1
docs/swagger-ui.js.map
Normal file
1
docs/swagger-ui.js.map
Normal file
File diff suppressed because one or more lines are too long
382
docs/swagger.json
Normal file
382
docs/swagger.json
Normal file
|
@ -0,0 +1,382 @@
|
|||
{
|
||||
"openapi": "3.0.0",
|
||||
"info": {
|
||||
"version": "0.1.0",
|
||||
"title": "mCaptcha/guard"
|
||||
},
|
||||
"servers": [
|
||||
{
|
||||
"url": "/"
|
||||
}
|
||||
],
|
||||
"paths": {
|
||||
"/api/v1/signup": {
|
||||
"post": {
|
||||
"summary": "Registration endpoint",
|
||||
"operationId": "registerUser",
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/RegisterUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful registration"
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad request: username contains profainity/blacklisted words or email not acceptable or password too long/short or duplicate username/password",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/signin": {
|
||||
"post": {
|
||||
"summary": "Login endpoint",
|
||||
"operationId": "loginUser",
|
||||
"tags": [
|
||||
"user",
|
||||
"authentication"
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/LoginUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Successful authentication"
|
||||
},
|
||||
"401": {
|
||||
"description": "authentication failed, wrong password",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "username not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/signout": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"summary": "Signout endpoint",
|
||||
"operationId": "signoutUser",
|
||||
"tags": [
|
||||
"user",
|
||||
"authentication"
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/account/delete": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"cookieAuth": []
|
||||
}
|
||||
],
|
||||
"summary": "Delete user account",
|
||||
"operationId": "deleteUserAccount",
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/DeleteUser"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"401": {
|
||||
"description": "(cookie)authentication required or wrong password",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "username not found",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/account/username/exists": {
|
||||
"post": {
|
||||
"summary": "Check if username exists",
|
||||
"operationId": "usernameExists",
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UserDetailCheck"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UserDetailCheckRes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/account/email/exists": {
|
||||
"post": {
|
||||
"summary": "Check if email exists",
|
||||
"operationId": "emailExists",
|
||||
"tags": [
|
||||
"user"
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UserDetailCheck"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UserDetailCheckRes"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal server error",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/Error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"components": {
|
||||
"schemas": {
|
||||
"RegisterUser": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username",
|
||||
"password",
|
||||
"email"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"format": "password"
|
||||
}
|
||||
}
|
||||
},
|
||||
"LoginUser": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"username",
|
||||
"password"
|
||||
],
|
||||
"properties": {
|
||||
"username": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"format": "password"
|
||||
}
|
||||
}
|
||||
},
|
||||
"DeleteUser": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"password"
|
||||
],
|
||||
"properties": {
|
||||
"password": {
|
||||
"type": "string",
|
||||
"format": "password"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Error": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"error"
|
||||
],
|
||||
"properties": {
|
||||
"error": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"User": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "integer",
|
||||
"format": "int64"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserDetailCheck": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"val"
|
||||
],
|
||||
"properties": {
|
||||
"val": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"UserDetailCheckRes": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"exists"
|
||||
],
|
||||
"properties": {
|
||||
"val": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"securitySchemes": {
|
||||
"cookieAuth": {
|
||||
"type": "apiKey",
|
||||
"in": "cookie",
|
||||
"name": "Authorization"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
246
openapi.yaml
Normal file
246
openapi.yaml
Normal file
|
@ -0,0 +1,246 @@
|
|||
openapi: 3.0.0
|
||||
info:
|
||||
version: 0.1.0
|
||||
title: mCaptcha/guard
|
||||
servers:
|
||||
- url: 'http://localhost:3000'
|
||||
|
||||
paths:
|
||||
/api/v1/signup:
|
||||
post:
|
||||
summary: 'Registration endpoint'
|
||||
operationId: registerUser
|
||||
tags:
|
||||
- user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/RegisterUser'
|
||||
responses:
|
||||
'200':
|
||||
description: 'Successful registration'
|
||||
'500':
|
||||
description: 'Internal server error'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'400':
|
||||
description: 'Bad request: username contains profainity/blacklisted words or email not acceptable or password too long/short or duplicate username/password'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/api/v1/signin:
|
||||
post:
|
||||
summary: 'Login endpoint'
|
||||
operationId: loginUser
|
||||
tags:
|
||||
- user
|
||||
- authentication
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LoginUser'
|
||||
responses:
|
||||
'200':
|
||||
description: 'Successful authentication'
|
||||
'500':
|
||||
description: 'Internal server error'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: 'authentication failed, wrong password'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'404':
|
||||
description: 'username not found'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/api/v1/signout:
|
||||
post:
|
||||
security:
|
||||
- cookieAuth: []
|
||||
summary: 'Signout endpoint'
|
||||
operationId: signoutUser
|
||||
tags:
|
||||
- user
|
||||
- authentication
|
||||
responses:
|
||||
'200':
|
||||
description: 'OK'
|
||||
|
||||
/api/v1/account/delete:
|
||||
post:
|
||||
security:
|
||||
- cookieAuth: []
|
||||
summary: 'Delete user account'
|
||||
operationId: deleteUserAccount
|
||||
tags:
|
||||
- user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/DeleteUser'
|
||||
responses:
|
||||
'200':
|
||||
description: 'OK'
|
||||
'500':
|
||||
description: 'Internal server error'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: '(cookie)authentication required or wrong password'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'404':
|
||||
description: 'username not found'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/api/v1/account/username/exists:
|
||||
post:
|
||||
summary: 'Check if username exists'
|
||||
operationId: usernameExists
|
||||
tags:
|
||||
- user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserDetailCheck'
|
||||
responses:
|
||||
'200':
|
||||
description: 'OK'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserDetailCheckRes'
|
||||
'500':
|
||||
description: 'Internal server error'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
|
||||
/api/v1/account/email/exists:
|
||||
post:
|
||||
summary: 'Check if email exists'
|
||||
operationId: emailExists
|
||||
tags:
|
||||
- user
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserDetailCheck'
|
||||
responses:
|
||||
'200':
|
||||
description: 'OK'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/UserDetailCheckRes'
|
||||
'500':
|
||||
description: 'Internal server error'
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
components:
|
||||
schemas:
|
||||
RegisterUser:
|
||||
type: object
|
||||
required:
|
||||
- username
|
||||
- password
|
||||
- email
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
email:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
format: password
|
||||
LoginUser:
|
||||
type: object
|
||||
required:
|
||||
- username
|
||||
- password
|
||||
properties:
|
||||
username:
|
||||
type: string
|
||||
password:
|
||||
type: string
|
||||
format: password
|
||||
DeleteUser:
|
||||
type: object
|
||||
required:
|
||||
- password
|
||||
properties:
|
||||
password:
|
||||
type: string
|
||||
format: password
|
||||
Error:
|
||||
type: object
|
||||
required:
|
||||
- error
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
User:
|
||||
type: object
|
||||
required:
|
||||
- id
|
||||
- name
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
format: int64
|
||||
name:
|
||||
type: string
|
||||
UserDetailCheck:
|
||||
type: object
|
||||
required:
|
||||
- val
|
||||
properties:
|
||||
val:
|
||||
type: string
|
||||
UserDetailCheckRes:
|
||||
type: object
|
||||
required:
|
||||
- exists
|
||||
properties:
|
||||
val:
|
||||
type: boolean
|
||||
|
||||
securitySchemes:
|
||||
cookieAuth:
|
||||
type: apiKey
|
||||
in: cookie
|
||||
name: Authorization
|
|
@ -36,8 +36,9 @@ pub struct Login {
|
|||
pub password: String,
|
||||
}
|
||||
|
||||
struct Password {
|
||||
password: String,
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct Password {
|
||||
pub password: String,
|
||||
}
|
||||
|
||||
#[post("/api/v1/signup")]
|
||||
|
@ -114,7 +115,7 @@ pub fn is_authenticated(id: &Identity) -> ServiceResult<()> {
|
|||
#[post("/api/v1/account/delete")]
|
||||
pub async fn delete_account(
|
||||
id: Identity,
|
||||
payload: web::Json<Login>,
|
||||
payload: web::Json<Password>,
|
||||
data: web::Data<Data>,
|
||||
) -> ServiceResult<impl Responder> {
|
||||
use argon2_creds::Config;
|
||||
|
@ -122,10 +123,12 @@ pub async fn delete_account(
|
|||
|
||||
is_authenticated(&id)?;
|
||||
|
||||
let username = id.identity().unwrap();
|
||||
|
||||
let rec = sqlx::query_as!(
|
||||
Password,
|
||||
r#"SELECT password FROM mcaptcha_users WHERE name = ($1)"#,
|
||||
&payload.username,
|
||||
&username,
|
||||
)
|
||||
.fetch_one(&data.db)
|
||||
.await;
|
||||
|
@ -135,12 +138,9 @@ pub async fn delete_account(
|
|||
match rec {
|
||||
Ok(s) => {
|
||||
if Config::verify(&s.password, &payload.password)? {
|
||||
sqlx::query!(
|
||||
"DELETE FROM mcaptcha_users WHERE name = ($1)",
|
||||
&payload.username,
|
||||
)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
sqlx::query!("DELETE FROM mcaptcha_users WHERE name = ($1)", &username)
|
||||
.execute(&data.db)
|
||||
.await?;
|
||||
Ok(HttpResponse::Ok())
|
||||
} else {
|
||||
Err(ServiceError::WrongPassword)
|
||||
|
@ -153,7 +153,7 @@ pub async fn delete_account(
|
|||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
pub struct AccountCheckPayload {
|
||||
pub field: String,
|
||||
pub val: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||
|
@ -168,7 +168,7 @@ pub async fn username_exists(
|
|||
) -> ServiceResult<impl Responder> {
|
||||
let res = sqlx::query!(
|
||||
"SELECT EXISTS (SELECT 1 from mcaptcha_users WHERE name = $1)",
|
||||
&payload.field,
|
||||
&payload.val,
|
||||
)
|
||||
.fetch_one(&data.db)
|
||||
.await?;
|
||||
|
@ -191,7 +191,7 @@ pub async fn email_exists(
|
|||
) -> ServiceResult<impl Responder> {
|
||||
let res = sqlx::query!(
|
||||
"SELECT EXISTS (SELECT 1 from mcaptcha_users WHERE email = $1)",
|
||||
&payload.field,
|
||||
&payload.val,
|
||||
)
|
||||
.fetch_one(&data.db)
|
||||
.await?;
|
||||
|
|
|
@ -70,7 +70,7 @@ async fn auth_works() {
|
|||
SIGNIN,
|
||||
&login,
|
||||
ServiceError::UsernameNotFound,
|
||||
StatusCode::UNAUTHORIZED,
|
||||
StatusCode::NOT_FOUND,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
@ -115,9 +115,13 @@ async fn del_userworks() {
|
|||
let cookies = get_cookie!(signin_resp);
|
||||
let mut app = get_app!(data).await;
|
||||
|
||||
let payload = Password {
|
||||
password: creds.password,
|
||||
};
|
||||
|
||||
let delete_user_resp = test::call_service(
|
||||
&mut app,
|
||||
post_request!(&creds, "/api/v1/account/delete")
|
||||
post_request!(&payload, "/api/v1/account/delete")
|
||||
.cookie(cookies)
|
||||
.to_request(),
|
||||
)
|
||||
|
@ -143,7 +147,7 @@ async fn uname_email_exists_works() {
|
|||
let cookies = get_cookie!(signin_resp);
|
||||
let mut app = get_app!(data).await;
|
||||
|
||||
let mut payload = AccountCheckPayload { field: NAME.into() };
|
||||
let mut payload = AccountCheckPayload { val: NAME.into() };
|
||||
|
||||
let user_exists_resp = test::call_service(
|
||||
&mut app,
|
||||
|
@ -156,7 +160,7 @@ async fn uname_email_exists_works() {
|
|||
let mut resp: AccountCheckResp = test::read_body_json(user_exists_resp).await;
|
||||
assert!(resp.exists);
|
||||
|
||||
payload.field = PASSWORD.into();
|
||||
payload.val = PASSWORD.into();
|
||||
|
||||
let user_doesnt_exist = test::call_service(
|
||||
&mut app,
|
||||
|
@ -180,7 +184,7 @@ async fn uname_email_exists_works() {
|
|||
resp = test::read_body_json(email_doesnt_exist).await;
|
||||
assert!(!resp.exists);
|
||||
|
||||
payload.field = EMAIL.into();
|
||||
payload.val = EMAIL.into();
|
||||
|
||||
let email_exist = test::call_service(
|
||||
&mut app,
|
||||
|
|
82
src/docs.rs
Normal file
82
src/docs.rs
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use actix_web::body::Body;
|
||||
use actix_web::{get, web, HttpResponse, Responder};
|
||||
use mime_guess::from_path;
|
||||
use rust_embed::RustEmbed;
|
||||
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(RustEmbed)]
|
||||
#[folder = "docs/"]
|
||||
struct Asset;
|
||||
|
||||
pub fn handle_embedded_file(path: &str) -> HttpResponse {
|
||||
match Asset::get(path) {
|
||||
Some(content) => {
|
||||
let body: Body = match content {
|
||||
Cow::Borrowed(bytes) => bytes.into(),
|
||||
Cow::Owned(bytes) => bytes.into(),
|
||||
};
|
||||
HttpResponse::Ok()
|
||||
.content_type(from_path(path).first_or_octet_stream().as_ref())
|
||||
.body(body)
|
||||
}
|
||||
None => HttpResponse::NotFound().body("404 Not Found"),
|
||||
}
|
||||
}
|
||||
|
||||
#[get("/docs/{_:.*}")]
|
||||
async fn dist(path: web::Path<String>) -> impl Responder {
|
||||
handle_embedded_file(&path.0)
|
||||
}
|
||||
|
||||
#[get("/docs")]
|
||||
async fn index() -> HttpResponse {
|
||||
handle_embedded_file("index.html")
|
||||
}
|
||||
|
||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(index);
|
||||
cfg.service(dist);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use actix_web::http::StatusCode;
|
||||
use actix_web::test;
|
||||
|
||||
use super::*;
|
||||
use crate::*;
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn docs_work() {
|
||||
const INDEX: &str = "/docs";
|
||||
const FILE: &str = "/docs/swagger.json";
|
||||
|
||||
let mut app = test::init_service(App::new().configure(services)).await;
|
||||
|
||||
let resp =
|
||||
test::call_service(&mut app, test::TestRequest::get().uri(INDEX).to_request()).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
|
||||
let resp =
|
||||
test::call_service(&mut app, test::TestRequest::get().uri(FILE).to_request()).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
}
|
|
@ -118,14 +118,18 @@ impl ResponseError for ServiceError {
|
|||
ServiceError::NotAnEmail => StatusCode::BAD_REQUEST,
|
||||
ServiceError::NotAUrl => StatusCode::BAD_REQUEST,
|
||||
ServiceError::WrongPassword => StatusCode::UNAUTHORIZED,
|
||||
ServiceError::UsernameNotFound => StatusCode::UNAUTHORIZED,
|
||||
ServiceError::UsernameNotFound => StatusCode::NOT_FOUND,
|
||||
ServiceError::AuthorizationRequired => StatusCode::UNAUTHORIZED,
|
||||
|
||||
ServiceError::ProfainityError => StatusCode::BAD_REQUEST,
|
||||
ServiceError::BlacklistError => StatusCode::BAD_REQUEST,
|
||||
ServiceError::UsernameCaseMappedError => StatusCode::BAD_REQUEST,
|
||||
|
||||
ServiceError::PasswordTooShort => StatusCode::BAD_REQUEST,
|
||||
ServiceError::PasswordTooLong => StatusCode::BAD_REQUEST,
|
||||
ServiceError::UsernameCaseMappedError => StatusCode::BAD_REQUEST,
|
||||
|
||||
ServiceError::UsernameTaken => StatusCode::BAD_REQUEST,
|
||||
|
||||
ServiceError::TokenNameTaken => StatusCode::BAD_REQUEST,
|
||||
ServiceError::TokenNotFound => StatusCode::NOT_FOUND,
|
||||
ServiceError::HostnameTaken => StatusCode::BAD_REQUEST,
|
||||
|
|
|
@ -30,6 +30,7 @@ mod data;
|
|||
mod errors;
|
||||
//mod routes;
|
||||
mod api;
|
||||
mod docs;
|
||||
mod settings;
|
||||
//mod templates;
|
||||
#[cfg(test)]
|
||||
|
@ -54,7 +55,9 @@ pub static VERIFICATION_PATH: &str = "mcaptchaVerificationChallenge.json";
|
|||
#[cfg(not(tarpaulin_include))]
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
use api::v1::services as v1_services;
|
||||
use actix_web::web;
|
||||
use api::v1;
|
||||
use docs;
|
||||
pretty_env_logger::init();
|
||||
info!(
|
||||
"{}: {}.\nFor more information, see: {}\nBuild info:\nVersion: {} commit: {}",
|
||||
|
@ -67,7 +70,9 @@ async fn main() -> std::io::Result<()> {
|
|||
HttpServer::new(move || {
|
||||
let client = Client::default();
|
||||
App::new()
|
||||
.configure(v1_services)
|
||||
.configure(v1::services)
|
||||
.configure(docs::services)
|
||||
//.service(web::resource("/dist/{_:.*}").route(web::get().to(docs::dist)))
|
||||
.wrap(middleware::Logger::default())
|
||||
.wrap(get_identity_service())
|
||||
.wrap(middleware::Compress::default())
|
||||
|
|
Loading…
Add table
Reference in a new issue