Add /api/v1/apps endpoint
This commit is contained in:
parent
8958dca939
commit
2d9a43b076
13 changed files with 232 additions and 2 deletions
|
@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
|
||||
### Added
|
||||
|
||||
- Added `/api/v1/apps` endpoint.
|
||||
- Documented `http_cors_allowlist` configuration parameter.
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -584,6 +584,47 @@ paths:
|
|||
$ref: '#/components/schemas/Relationship'
|
||||
404:
|
||||
description: Profile not found
|
||||
/api/v1/apps:
|
||||
post:
|
||||
summary: Create a new application to obtain OAuth2 credentials.
|
||||
requestBody:
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
client_name:
|
||||
description: A name for your application.
|
||||
type: string
|
||||
redirect_uris:
|
||||
description: Where the user should be redirected after authorization.
|
||||
type: string
|
||||
scopes:
|
||||
description: Space separated list of scopes.
|
||||
type: string
|
||||
example: 'read write'
|
||||
website:
|
||||
description: An URL to the homepage of your app.
|
||||
type: string
|
||||
nullable: true
|
||||
responses:
|
||||
200:
|
||||
description: Successful operation
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
allOf:
|
||||
- $ref: '#/components/schemas/Application'
|
||||
- type: object
|
||||
properties:
|
||||
client_id:
|
||||
description: Client ID key, to be used for obtaining OAuth tokens.
|
||||
type: string
|
||||
client_secret:
|
||||
description: Client secret key, to be used for obtaining OAuth tokens.
|
||||
type: string
|
||||
400:
|
||||
description: Invalid request data.
|
||||
/api/v1/custom_emojis:
|
||||
get:
|
||||
summary: Returns custom emojis that are available on the server.
|
||||
|
@ -1360,6 +1401,19 @@ components:
|
|||
type: string
|
||||
enum:
|
||||
- update
|
||||
Application:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
description: The name of your application.
|
||||
type: string
|
||||
website:
|
||||
description: The website associated with your application.
|
||||
type: string
|
||||
nullable: true
|
||||
redirect_uri:
|
||||
description: Where the user should be redirected after authorization.
|
||||
type: string
|
||||
Attachment:
|
||||
type: object
|
||||
properties:
|
||||
|
|
10
migrations/V0042__oauth_application.sql
Normal file
10
migrations/V0042__oauth_application.sql
Normal file
|
@ -0,0 +1,10 @@
|
|||
CREATE TABLE oauth_application (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
app_name VARCHAR(100) NOT NULL,
|
||||
website VARCHAR(100),
|
||||
scopes VARCHAR(200) NOT NULL,
|
||||
redirect_uri VARCHAR(200) NOT NULL,
|
||||
client_id UUID UNIQUE NOT NULL,
|
||||
client_secret VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
|
@ -51,6 +51,17 @@ CREATE TABLE user_account (
|
|||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE oauth_application (
|
||||
id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
|
||||
app_name VARCHAR(100) NOT NULL,
|
||||
website VARCHAR(100),
|
||||
scopes VARCHAR(200) NOT NULL,
|
||||
redirect_uri VARCHAR(200) NOT NULL,
|
||||
client_id UUID UNIQUE NOT NULL,
|
||||
client_secret VARCHAR(100) NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
CREATE TABLE oauth_token (
|
||||
id SERIAL PRIMARY KEY,
|
||||
owner_id UUID NOT NULL REFERENCES user_account (id) ON DELETE CASCADE,
|
||||
|
|
|
@ -20,6 +20,7 @@ use mitra::http::json_error_handler;
|
|||
use mitra::job_queue::scheduler;
|
||||
use mitra::logger::configure_logger;
|
||||
use mitra::mastodon_api::accounts::views::account_api_scope;
|
||||
use mitra::mastodon_api::apps::views::application_api_scope;
|
||||
use mitra::mastodon_api::custom_emojis::views::custom_emoji_api_scope;
|
||||
use mitra::mastodon_api::directory::views::directory_api_scope;
|
||||
use mitra::mastodon_api::instance::views::instance_api_scope;
|
||||
|
@ -145,6 +146,7 @@ async fn main() -> std::io::Result<()> {
|
|||
))
|
||||
.service(oauth_api_scope())
|
||||
.service(account_api_scope())
|
||||
.service(application_api_scope())
|
||||
.service(custom_emoji_api_scope())
|
||||
.service(directory_api_scope())
|
||||
.service(instance_api_scope())
|
||||
|
|
2
src/mastodon_api/apps/mod.rs
Normal file
2
src/mastodon_api/apps/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
mod types;
|
||||
pub mod views;
|
20
src/mastodon_api/apps/types.rs
Normal file
20
src/mastodon_api/apps/types.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CreateAppRequest {
|
||||
pub client_name: String,
|
||||
pub redirect_uris: String,
|
||||
pub scopes: String,
|
||||
pub website: Option<String>,
|
||||
}
|
||||
|
||||
/// https://docs.joinmastodon.org/entities/Application/
|
||||
#[derive(Serialize)]
|
||||
pub struct OauthApp {
|
||||
pub name: String,
|
||||
pub website: Option<String>,
|
||||
pub redirect_uri: String,
|
||||
pub client_id: Option<Uuid>,
|
||||
pub client_secret: Option<String>,
|
||||
}
|
55
src/mastodon_api/apps/views.rs
Normal file
55
src/mastodon_api/apps/views.rs
Normal file
|
@ -0,0 +1,55 @@
|
|||
use actix_web::{
|
||||
post,
|
||||
web,
|
||||
Either,
|
||||
HttpResponse,
|
||||
Scope,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::{get_database_client, DbPool};
|
||||
use crate::errors::HttpError;
|
||||
use crate::mastodon_api::oauth::utils::generate_access_token;
|
||||
use crate::models::{
|
||||
oauth::queries::create_oauth_app,
|
||||
oauth::types::DbOauthAppData,
|
||||
};
|
||||
use super::types::{OauthApp, CreateAppRequest};
|
||||
|
||||
/// https://docs.joinmastodon.org/methods/apps/
|
||||
#[post("")]
|
||||
async fn create_app_view(
|
||||
db_pool: web::Data<DbPool>,
|
||||
request_data: Either<
|
||||
web::Json<CreateAppRequest>,
|
||||
web::Form<CreateAppRequest>,
|
||||
>,
|
||||
) -> Result<HttpResponse, HttpError> {
|
||||
let request_data = match request_data {
|
||||
Either::Left(json) => json.into_inner(),
|
||||
Either::Right(form) => form.into_inner(),
|
||||
};
|
||||
let db_client = &**get_database_client(&db_pool).await?;
|
||||
let db_app_data = DbOauthAppData {
|
||||
app_name: request_data.client_name,
|
||||
website: request_data.website,
|
||||
scopes: request_data.scopes,
|
||||
redirect_uri: request_data.redirect_uris,
|
||||
client_id: Uuid::new_v4(),
|
||||
client_secret: generate_access_token(),
|
||||
};
|
||||
let db_app = create_oauth_app(db_client, db_app_data).await?;
|
||||
let app = OauthApp {
|
||||
name: db_app.app_name,
|
||||
website: db_app.website,
|
||||
redirect_uri: db_app.redirect_uri,
|
||||
client_id: Some(db_app.client_id),
|
||||
client_secret: Some(db_app.client_secret),
|
||||
};
|
||||
Ok(HttpResponse::Ok().json(app))
|
||||
}
|
||||
|
||||
pub fn application_api_scope() -> Scope {
|
||||
web::scope("/api/v1/apps")
|
||||
.service(create_app_view)
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
pub mod accounts;
|
||||
pub mod apps;
|
||||
pub mod custom_emojis;
|
||||
pub mod directory;
|
||||
pub mod instance;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
pub mod auth;
|
||||
mod types;
|
||||
pub mod views;
|
||||
mod utils;
|
||||
pub mod utils;
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod types;
|
||||
pub mod queries;
|
||||
|
|
|
@ -1,9 +1,44 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use uuid::Uuid;
|
||||
|
||||
use crate::database::{DatabaseClient, DatabaseError};
|
||||
use crate::database::{
|
||||
catch_unique_violation,
|
||||
DatabaseClient,
|
||||
DatabaseError,
|
||||
};
|
||||
use crate::models::profiles::types::DbActorProfile;
|
||||
use crate::models::users::types::{DbUser, User};
|
||||
use super::types::{DbOauthApp, DbOauthAppData};
|
||||
|
||||
pub async fn create_oauth_app(
|
||||
db_client: &impl DatabaseClient,
|
||||
app_data: DbOauthAppData,
|
||||
) -> Result<DbOauthApp, DatabaseError> {
|
||||
let row = db_client.query_one(
|
||||
"
|
||||
INSERT INTO oauth_application (
|
||||
app_name,
|
||||
website,
|
||||
scopes,
|
||||
redirect_uri,
|
||||
client_id,
|
||||
client_secret
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
RETURNING oauth_application
|
||||
",
|
||||
&[
|
||||
&app_data.app_name,
|
||||
&app_data.website,
|
||||
&app_data.scopes,
|
||||
&app_data.redirect_uri,
|
||||
&app_data.client_id,
|
||||
&app_data.client_secret,
|
||||
],
|
||||
).await.map_err(catch_unique_violation("oauth_application"))?;
|
||||
let app = row.try_get("oauth_application")?;
|
||||
Ok(app)
|
||||
}
|
||||
|
||||
pub async fn save_oauth_token(
|
||||
db_client: &impl DatabaseClient,
|
||||
|
@ -94,6 +129,18 @@ mod tests {
|
|||
use crate::models::users::types::UserCreateData;
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_create_oauth_app() {
|
||||
let db_client = &create_test_database().await;
|
||||
let db_app_data = DbOauthAppData {
|
||||
app_name: "My App".to_string(),
|
||||
..Default::default()
|
||||
};
|
||||
let app = create_oauth_app(db_client, db_app_data).await.unwrap();
|
||||
assert_eq!(app.app_name, "My App");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[serial]
|
||||
async fn test_delete_oauth_token() {
|
||||
|
|
26
src/models/oauth/types.rs
Normal file
26
src/models/oauth/types.rs
Normal file
|
@ -0,0 +1,26 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use postgres_types::FromSql;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(FromSql)]
|
||||
#[postgres(name = "oauth_application")]
|
||||
pub struct DbOauthApp {
|
||||
pub id: i32,
|
||||
pub app_name: String,
|
||||
pub website: Option<String>,
|
||||
pub scopes: String,
|
||||
pub redirect_uri: String,
|
||||
pub client_id: Uuid,
|
||||
pub client_secret: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(Default))]
|
||||
pub struct DbOauthAppData {
|
||||
pub app_name: String,
|
||||
pub website: Option<String>,
|
||||
pub scopes: String,
|
||||
pub redirect_uri: String,
|
||||
pub client_id: Uuid,
|
||||
pub client_secret: String,
|
||||
}
|
Loading…
Reference in a new issue