diff --git a/agdb_api/typescript/src/schema.d.ts b/agdb_api/typescript/src/schema.d.ts index 182d480d4..2e4957339 100644 --- a/agdb_api/typescript/src/schema.d.ts +++ b/agdb_api/typescript/src/schema.d.ts @@ -1057,6 +1057,10 @@ declare namespace Components { export interface UserCredentials { password: string; } + export interface UserLogin { + password: string; + username: string; + } export interface UserStatus { name: string; } @@ -1689,19 +1693,11 @@ declare namespace Paths { } } namespace UserLogin { - namespace Parameters { - export type Username = string; - } - export interface PathParameters { - username: Parameters.Username; - } - export type RequestBody = Components.Schemas.UserCredentials; + export type RequestBody = Components.Schemas.UserLogin; namespace Responses { export type $200 = string; export interface $401 { } - export interface $404 { - } } } } @@ -1963,6 +1959,14 @@ export interface OperationMethods { data?: any, config?: AxiosRequestConfig ): OperationResponse + /** + * user_login + */ + 'user_login'( + parameters?: Parameters | null, + data?: Paths.UserLogin.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse /** * user_change_password */ @@ -1971,14 +1975,6 @@ export interface OperationMethods { data?: Paths.UserChangePassword.RequestBody, config?: AxiosRequestConfig ): OperationResponse - /** - * user_login - */ - 'user_login'( - parameters?: Parameters | null, - data?: Paths.UserLogin.RequestBody, - config?: AxiosRequestConfig - ): OperationResponse } export interface PathsDictionary { @@ -2302,6 +2298,16 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } + ['/api/v1/user/login']: { + /** + * user_login + */ + 'post'( + parameters?: Parameters | null, + data?: Paths.UserLogin.RequestBody, + config?: AxiosRequestConfig + ): OperationResponse + } ['/api/v1/user/{username}/change_password']: { /** * user_change_password @@ -2312,16 +2318,6 @@ export interface PathsDictionary { config?: AxiosRequestConfig ): OperationResponse } - ['/api/v1/user/{username}/login']: { - /** - * user_login - */ - 'post'( - parameters?: Parameters | null, - data?: Paths.UserLogin.RequestBody, - config?: AxiosRequestConfig - ): OperationResponse - } } export type Client = OpenAPIClient diff --git a/agdb_api/typescript/tests/OpenApi.test.ts b/agdb_api/typescript/tests/OpenApi.test.ts index 2a13441c6..230e950ed 100644 --- a/agdb_api/typescript/tests/OpenApi.test.ts +++ b/agdb_api/typescript/tests/OpenApi.test.ts @@ -5,11 +5,11 @@ import { Api } from "./client"; describe("openapi test", () => { it("insert node", async () => { let client = await Api.client(); - let admin_token = await client.user_login("admin", { password: "admin" }); + let admin_token = await client.user_login(null, { username: "admin", password: "admin" }); Api.setToken(admin_token.data); await client.admin_user_add("user1", { password: "password123" }); - let token = await client.user_login("user1", { password: "password123" }); + let token = await client.user_login(null, { username: "user1", password: "password123" }); Api.setToken(token.data); await client.db_add({ diff --git a/agdb_server/openapi/schema.json b/agdb_server/openapi/schema.json index cc9a059ab..c3f206f37 100644 --- a/agdb_server/openapi/schema.json +++ b/agdb_server/openapi/schema.json @@ -1559,60 +1559,45 @@ } } }, - "/api/v1/user/{username}/change_password": { - "put": { + "/api/v1/user/login": { + "post": { "tags": [ "crate::routes::user" ], - "operationId": "user_change_password", - "parameters": [ - { - "name": "username", - "in": "path", - "description": "username", - "required": true, - "schema": { - "type": "string" - } - } - ], + "operationId": "user_login", "requestBody": { "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/ChangePassword" + "$ref": "#/components/schemas/UserLogin" } } }, "required": true }, "responses": { - "201": { - "description": "password changed" + "200": { + "description": "login successful", + "content": { + "text/plain": { + "schema": { + "type": "string" + } + } + } }, "401": { "description": "invalid credentials" - }, - "404": { - "description": "user not found" - }, - "461": { - "description": "password too short (<8)" - } - }, - "security": [ - { - "Token": [] } - ] + } } }, - "/api/v1/user/{username}/login": { - "post": { + "/api/v1/user/{username}/change_password": { + "put": { "tags": [ "crate::routes::user" ], - "operationId": "user_login", + "operationId": "user_change_password", "parameters": [ { "name": "username", @@ -1628,30 +1613,31 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/UserCredentials" + "$ref": "#/components/schemas/ChangePassword" } } }, "required": true }, "responses": { - "200": { - "description": "login successful", - "content": { - "text/plain": { - "schema": { - "type": "string" - } - } - } + "201": { + "description": "password changed" }, "401": { "description": "invalid credentials" }, "404": { "description": "user not found" + }, + "461": { + "description": "password too short (<8)" } - } + }, + "security": [ + { + "Token": [] + } + ] } } }, @@ -2823,6 +2809,21 @@ } } }, + "UserLogin": { + "type": "object", + "required": [ + "username", + "password" + ], + "properties": { + "password": { + "type": "string" + }, + "username": { + "type": "string" + } + } + }, "UserStatus": { "type": "object", "required": [ diff --git a/agdb_server/src/api.rs b/agdb_server/src/api.rs index 2ebb3110f..39d42808b 100644 --- a/agdb_server/src/api.rs +++ b/agdb_server/src/api.rs @@ -57,6 +57,7 @@ use utoipa::OpenApi; crate::routes::db::user::DbUserRole, crate::routes::db::user::DbUserRoleParam, crate::routes::user::ChangePassword, + crate::routes::user::UserLogin, crate::routes::user::UserCredentials, agdb::QueryResult, agdb::DbElement, diff --git a/agdb_server/src/app.rs b/agdb_server/src/app.rs index 99c892c7f..8cb662900 100644 --- a/agdb_server/src/app.rs +++ b/agdb_server/src/app.rs @@ -109,7 +109,7 @@ pub(crate) fn app(config: Config, shutdown_sender: Sender<()>, db_pool: DbPool) "/db/:user/:db/user/:other/remove", routing::delete(routes::db::user::remove), ) - .route("/user/:user/login", routing::post(routes::user::login)) + .route("/user/login", routing::post(routes::user::login)) .route( "/user/:user/change_password", routing::put(routes::user::change_password), diff --git a/agdb_server/src/routes/user.rs b/agdb_server/src/routes/user.rs index 4b22c350e..d23dc8c40 100644 --- a/agdb_server/src/routes/user.rs +++ b/agdb_server/src/routes/user.rs @@ -1,5 +1,6 @@ use crate::db_pool::DbPool; use crate::password::Password; +use crate::server_error::ServerError; use crate::server_error::ServerResponse; use axum::extract::Path; use axum::extract::State; @@ -14,6 +15,12 @@ pub(crate) struct UserCredentials { pub(crate) password: String, } +#[derive(Deserialize, ToSchema)] +pub(crate) struct UserLogin { + pub(crate) username: String, + pub(crate) password: String, +} + #[derive(Deserialize, ToSchema)] pub(crate) struct ChangePassword { pub(crate) password: String, @@ -21,28 +28,25 @@ pub(crate) struct ChangePassword { } #[utoipa::path(post, - path = "/api/v1/user/{username}/login", + path = "/api/v1/user/login", operation_id = "user_login", - params( - ("username" = String, Path, description = "username"), - ), - request_body = UserCredentials, + request_body = UserLogin, responses( (status = 200, description = "login successful", body = String), (status = 401, description = "invalid credentials"), - (status = 404, description = "user not found") ) )] pub(crate) async fn login( State(db_pool): State, - Path(username): Path, - Json(request): Json, + Json(request): Json, ) -> ServerResponse<(StatusCode, String)> { - let user = db_pool.find_user(&username)?; + let user = db_pool + .find_user(&request.username) + .map_err(|_| ServerError::new(StatusCode::UNAUTHORIZED, "unuauthorized"))?; let pswd = Password::new(&user.name, &user.password, &user.salt)?; if !pswd.verify_password(&request.password) { - return Ok((StatusCode::UNAUTHORIZED, String::new())); + return Err(ServerError::new(StatusCode::UNAUTHORIZED, "unuauthorized")); } let token_uuid = Uuid::new_v4(); @@ -76,7 +80,7 @@ pub(crate) async fn change_password( let old_pswd = Password::new(&user.name, &user.password, &user.salt)?; if !old_pswd.verify_password(&request.password) { - return Ok(StatusCode::UNAUTHORIZED); + return Err(ServerError::new(StatusCode::UNAUTHORIZED, "unuauthorized")); } db_pool.change_password(user, &request.new_password)?; diff --git a/agdb_server/tests/integration.rs b/agdb_server/tests/integration.rs index a027f9b5b..9ab2e0947 100644 --- a/agdb_server/tests/integration.rs +++ b/agdb_server/tests/integration.rs @@ -56,6 +56,12 @@ pub struct UserCredentials<'a> { pub password: &'a str, } +#[derive(Serialize, Deserialize)] +pub struct UserLogin<'a> { + pub username: &'a str, + pub password: &'a str, +} + #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct UserStatus { pub name: String, @@ -116,13 +122,12 @@ impl TestServer { .await { Ok(_) => { - let credentials = UserCredentials { password: ADMIN }; + let credentials = UserLogin { + username: ADMIN, + password: ADMIN, + }; let response = client - .post(format!( - "{}:{}/api/v1/user/{ADMIN}/login", - Self::url_base(), - port - )) + .post(format!("{}:{}/api/v1/user/login", Self::url_base(), port)) .json(&credentials) .send() .await?; @@ -193,7 +198,14 @@ impl TestServer { 201 ); let response = self - .post(&format!("/user/{name}/login"), &credentials, &None) + .post( + "/user/login", + &Some(UserLogin { + username: &name, + password: &name, + }), + &None, + ) .await?; assert_eq!(response.0, 200); Ok(ServerUser { diff --git a/agdb_server/tests/routes/admin_user_change_password_test.rs b/agdb_server/tests/routes/admin_user_change_password_test.rs index f6a5a88d5..428342496 100644 --- a/agdb_server/tests/routes/admin_user_change_password_test.rs +++ b/agdb_server/tests/routes/admin_user_change_password_test.rs @@ -1,5 +1,6 @@ use crate::TestServer; use crate::UserCredentials; +use crate::UserLogin; use crate::NO_TOKEN; #[tokio::test] @@ -20,17 +21,13 @@ async fn change_password() -> anyhow::Result<()> { .await?, 201 ); - assert_eq!( - server - .post( - &format!("/user/{}/login", user.name), - &credentials, - NO_TOKEN - ) - .await? - .0, - 200 - ); + + let login = Some(UserLogin { + password: "password456", + username: &user.name, + }); + + assert_eq!(server.post("/user/login", &login, NO_TOKEN).await?.0, 200); Ok(()) } diff --git a/agdb_server/tests/routes/user_change_password_test.rs b/agdb_server/tests/routes/user_change_password_test.rs index 73873a5eb..ef2162112 100644 --- a/agdb_server/tests/routes/user_change_password_test.rs +++ b/agdb_server/tests/routes/user_change_password_test.rs @@ -1,13 +1,14 @@ use crate::ChangePassword; use crate::TestServer; -use crate::UserCredentials; +use crate::UserLogin; use crate::NO_TOKEN; #[tokio::test] async fn change_password() -> anyhow::Result<()> { let server = TestServer::new().await?; let user = server.init_user().await?; - let credentials = Some(UserCredentials { + let credentials = Some(UserLogin { + username: &user.name, password: "password456", }); let change: Option = Some(ChangePassword { @@ -25,14 +26,7 @@ async fn change_password() -> anyhow::Result<()> { 201 ); assert_eq!( - server - .post( - &format!("/user/{}/login", user.name), - &credentials, - NO_TOKEN - ) - .await? - .0, + server.post("/user/login", &credentials, NO_TOKEN).await?.0, 200 ); diff --git a/agdb_server/tests/routes/user_login_test.rs b/agdb_server/tests/routes/user_login_test.rs index eebe9acad..9d3aae90b 100644 --- a/agdb_server/tests/routes/user_login_test.rs +++ b/agdb_server/tests/routes/user_login_test.rs @@ -1,21 +1,16 @@ use crate::TestServer; -use crate::UserCredentials; +use crate::UserLogin; use crate::NO_TOKEN; #[tokio::test] async fn login() -> anyhow::Result<()> { let server = TestServer::new().await?; let user = server.init_user().await?; - let credentials = Some(UserCredentials { + let credentials = Some(UserLogin { password: &user.name, + username: &user.name, }); - let (status, token) = server - .post( - &format!("/user/{}/login", user.name), - &credentials, - NO_TOKEN, - ) - .await?; + let (status, token) = server.post("/user/login", &credentials, NO_TOKEN).await?; assert_eq!(status, 200); assert!(!token.is_empty()); @@ -26,18 +21,13 @@ async fn login() -> anyhow::Result<()> { async fn invalid_credentials() -> anyhow::Result<()> { let server = TestServer::new().await?; let user = server.init_user().await?; - let credentials = Some(UserCredentials { + let credentials = Some(UserLogin { + username: &user.name, password: "password456", }); - let (status, token) = server - .post( - &format!("/user/{}/login", user.name), - &credentials, - NO_TOKEN, - ) - .await?; + let (status, token) = server.post("/user/login", &credentials, NO_TOKEN).await?; assert_eq!(status, 401); - assert!(token.is_empty()); + assert_eq!(token, "unuauthorized"); Ok(()) } @@ -45,13 +35,12 @@ async fn invalid_credentials() -> anyhow::Result<()> { #[tokio::test] async fn user_not_found() -> anyhow::Result<()> { let server = TestServer::new().await?; - let user = Some(UserCredentials { + let user = Some(UserLogin { password: "password456", + username: "some_user", }); - let (status, _) = server - .post("/user/not_found/login", &user, NO_TOKEN) - .await?; - assert_eq!(status, 404); + let (status, _) = server.post("/user/login", &user, NO_TOKEN).await?; + assert_eq!(status, 401); Ok(()) }