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
|
||||||
|
|
||||||
|
- Added `/api/v1/apps` endpoint.
|
||||||
- Documented `http_cors_allowlist` configuration parameter.
|
- Documented `http_cors_allowlist` configuration parameter.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
|
@ -584,6 +584,47 @@ paths:
|
||||||
$ref: '#/components/schemas/Relationship'
|
$ref: '#/components/schemas/Relationship'
|
||||||
404:
|
404:
|
||||||
description: Profile not found
|
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:
|
/api/v1/custom_emojis:
|
||||||
get:
|
get:
|
||||||
summary: Returns custom emojis that are available on the server.
|
summary: Returns custom emojis that are available on the server.
|
||||||
|
@ -1360,6 +1401,19 @@ components:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
- update
|
- 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:
|
Attachment:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
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()
|
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 (
|
CREATE TABLE oauth_token (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
owner_id UUID NOT NULL REFERENCES user_account (id) ON DELETE CASCADE,
|
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::job_queue::scheduler;
|
||||||
use mitra::logger::configure_logger;
|
use mitra::logger::configure_logger;
|
||||||
use mitra::mastodon_api::accounts::views::account_api_scope;
|
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::custom_emojis::views::custom_emoji_api_scope;
|
||||||
use mitra::mastodon_api::directory::views::directory_api_scope;
|
use mitra::mastodon_api::directory::views::directory_api_scope;
|
||||||
use mitra::mastodon_api::instance::views::instance_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(oauth_api_scope())
|
||||||
.service(account_api_scope())
|
.service(account_api_scope())
|
||||||
|
.service(application_api_scope())
|
||||||
.service(custom_emoji_api_scope())
|
.service(custom_emoji_api_scope())
|
||||||
.service(directory_api_scope())
|
.service(directory_api_scope())
|
||||||
.service(instance_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 accounts;
|
||||||
|
pub mod apps;
|
||||||
pub mod custom_emojis;
|
pub mod custom_emojis;
|
||||||
pub mod directory;
|
pub mod directory;
|
||||||
pub mod instance;
|
pub mod instance;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
pub mod auth;
|
pub mod auth;
|
||||||
mod types;
|
mod types;
|
||||||
pub mod views;
|
pub mod views;
|
||||||
mod utils;
|
pub mod utils;
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
|
pub mod types;
|
||||||
pub mod queries;
|
pub mod queries;
|
||||||
|
|
|
@ -1,9 +1,44 @@
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use uuid::Uuid;
|
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::profiles::types::DbActorProfile;
|
||||||
use crate::models::users::types::{DbUser, User};
|
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(
|
pub async fn save_oauth_token(
|
||||||
db_client: &impl DatabaseClient,
|
db_client: &impl DatabaseClient,
|
||||||
|
@ -94,6 +129,18 @@ mod tests {
|
||||||
use crate::models::users::types::UserCreateData;
|
use crate::models::users::types::UserCreateData;
|
||||||
use super::*;
|
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]
|
#[tokio::test]
|
||||||
#[serial]
|
#[serial]
|
||||||
async fn test_delete_oauth_token() {
|
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