Streamlined grpc auth
Added ShareX Destiation on client
This commit is contained in:
@@ -21,3 +21,8 @@ input[type="text"] {
|
||||
border-radius: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
textarea {
|
||||
min-height: 200px;
|
||||
min-width: 500px;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ body {
|
||||
|
||||
#content {
|
||||
grid-area: Content;
|
||||
overflow-x: hidden;
|
||||
padding: 10px;
|
||||
/* margin-left: $navBarSize; */
|
||||
}
|
||||
@@ -106,3 +107,11 @@ form {
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
|
||||
.codeSelect {
|
||||
line-break: anywhere;
|
||||
white-space: pre-wrap;
|
||||
background-color: $featureColor;
|
||||
padding: 5px;
|
||||
user-select: all;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
use dioxus::prelude::*;
|
||||
use tonic::{IntoRequest, Request};
|
||||
use tonic::IntoRequest;
|
||||
|
||||
use crate::{
|
||||
components::MediaItem,
|
||||
contexts::AuthContext,
|
||||
rpc::{aoba::PageFilter, get_rpc_client},
|
||||
};
|
||||
|
||||
@@ -35,18 +34,9 @@ impl Into<PageFilter> for MediaGridProps {
|
||||
|
||||
#[component]
|
||||
pub fn MediaGrid(props: MediaGridProps) -> Element {
|
||||
let jwt = use_context::<AuthContext>().jwt;
|
||||
let media_result = use_resource(use_reactive!(|(props, jwt)| async move {
|
||||
let media_result = use_resource(use_reactive!(|(props)| async move {
|
||||
let mut client = get_rpc_client();
|
||||
let mut req = Request::new(props.into());
|
||||
let token = if jwt.cloned().is_some() {
|
||||
jwt.unwrap()
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
req.metadata_mut()
|
||||
.insert("authorization", format!("Bearer {token}").parse().unwrap());
|
||||
let result = client.list_media(req).await;
|
||||
let result = client.list_media(props.into_request()).await;
|
||||
return result.expect("Failed to load media").into_inner();
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
use dioxus::signals::{Signal, Writable};
|
||||
use web_sys::window;
|
||||
|
||||
use crate::rpc::{login, logout};
|
||||
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct AuthContext {
|
||||
pub jwt: Signal<Option<String>>,
|
||||
@@ -11,12 +13,14 @@ impl AuthContext {
|
||||
self.jwt.set(Some(token.clone()));
|
||||
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
|
||||
_ = local_storage.set_item("token", token.as_str());
|
||||
login(token.clone());
|
||||
}
|
||||
|
||||
pub fn logout(&mut self) {
|
||||
self.jwt.set(None);
|
||||
let local_storage = window().unwrap().local_storage().unwrap().unwrap();
|
||||
_ = local_storage.remove_item("token");
|
||||
logout();
|
||||
}
|
||||
|
||||
pub fn new() -> Self {
|
||||
@@ -25,17 +29,14 @@ impl AuthContext {
|
||||
match local_storage.get_item("token") {
|
||||
Ok(value) => {
|
||||
if let Some(jwt) = value {
|
||||
println!("jwt");
|
||||
login(jwt.clone());
|
||||
return AuthContext {
|
||||
jwt: Signal::new(Some(jwt)),
|
||||
};
|
||||
}
|
||||
return AuthContext::default();
|
||||
}
|
||||
Err(_) => {
|
||||
println!("err");
|
||||
AuthContext::default()
|
||||
}
|
||||
Err(_) => AuthContext::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::{components::Navbar, contexts::AuthContext, views::Login, Route};
|
||||
use crate::{Route, components::Navbar, contexts::AuthContext, views::Login};
|
||||
|
||||
#[component]
|
||||
pub fn MainLayout() -> Element {
|
||||
@@ -12,6 +12,9 @@ pub fn MainLayout() -> Element {
|
||||
|
||||
return rsx! {
|
||||
Navbar {}
|
||||
div{
|
||||
id: "content",
|
||||
Outlet::<Route> {}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::sync::RwLock;
|
||||
|
||||
use aoba::{aoba_rpc_client::AobaRpcClient, auth_rpc_client::AuthRpcClient};
|
||||
use tonic::service::{Interceptor, interceptor::InterceptedService};
|
||||
use tonic_web_wasm_client::Client;
|
||||
|
||||
use crate::HOST;
|
||||
@@ -13,16 +14,18 @@ pub mod aoba {
|
||||
static RPC_CLIENT: RpcConnection = RpcConnection {
|
||||
aoba: RwLock::new(None),
|
||||
auth: RwLock::new(None),
|
||||
jwt: RwLock::new(None),
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct RpcConnection {
|
||||
aoba: RwLock<Option<AobaRpcClient<Client>>>,
|
||||
aoba: RwLock<Option<AobaRpcClient<InterceptedService<Client, AuthInterceptor>>>>,
|
||||
auth: RwLock<Option<AuthRpcClient<Client>>>,
|
||||
jwt: RwLock<Option<String>>,
|
||||
}
|
||||
|
||||
impl RpcConnection {
|
||||
pub fn get_client(&self) -> AobaRpcClient<Client> {
|
||||
pub fn get_client(&self) -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
self.ensure_client();
|
||||
return self.aoba.read().unwrap().clone().unwrap();
|
||||
}
|
||||
@@ -35,16 +38,38 @@ impl RpcConnection {
|
||||
fn ensure_client(&self) {
|
||||
if self.aoba.read().unwrap().is_none() {
|
||||
let wasm_client = Client::new(HOST.into());
|
||||
*self.aoba.write().unwrap() = Some(AobaRpcClient::new(wasm_client.clone()));
|
||||
let aoba_client = AobaRpcClient::with_interceptor(wasm_client.clone(), AuthInterceptor);
|
||||
*self.aoba.write().unwrap() = Some(aoba_client);
|
||||
*self.auth.write().unwrap() = Some(AuthRpcClient::new(wasm_client.clone()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rpc_client() -> AobaRpcClient<Client> {
|
||||
#[derive(Clone)]
|
||||
pub struct AuthInterceptor;
|
||||
impl Interceptor for AuthInterceptor {
|
||||
fn call(&mut self, mut request: tonic::Request<()>) -> Result<tonic::Request<()>, tonic::Status> {
|
||||
if let Some(jwt) = RPC_CLIENT.jwt.read().unwrap().clone() {
|
||||
request
|
||||
.metadata_mut()
|
||||
.insert("authorization", format!("Bearer {jwt}").parse().unwrap());
|
||||
}
|
||||
return Ok(request);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_rpc_client() -> AobaRpcClient<InterceptedService<Client, AuthInterceptor>> {
|
||||
return RPC_CLIENT.get_client();
|
||||
}
|
||||
|
||||
pub fn get_auth_rpc_client() -> AuthRpcClient<Client> {
|
||||
return RPC_CLIENT.get_auth_client();
|
||||
}
|
||||
|
||||
pub fn login(jwt: String) {
|
||||
*RPC_CLIENT.jwt.write().unwrap() = Some(jwt);
|
||||
}
|
||||
|
||||
pub fn logout() {
|
||||
*RPC_CLIENT.jwt.write().unwrap() = None;
|
||||
}
|
||||
|
||||
@@ -6,12 +6,9 @@ pub fn Home() -> Element {
|
||||
let query = use_signal(|| "".to_string());
|
||||
|
||||
rsx! {
|
||||
div {
|
||||
id: "content",
|
||||
Search {
|
||||
query: query
|
||||
},
|
||||
MediaGrid { query: query.cloned() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,34 @@
|
||||
use dioxus::prelude::*;
|
||||
|
||||
use crate::rpc::get_rpc_client;
|
||||
|
||||
#[component]
|
||||
pub fn Settings() -> Element {
|
||||
rsx! { "this is settings" }
|
||||
let dst = use_resource(async move || {
|
||||
let result = get_rpc_client().get_share_x_destination(()).await;
|
||||
if let Ok(d) = result {
|
||||
if let Some(r) = d.into_inner().dst_result {
|
||||
return match r {
|
||||
crate::rpc::aoba::share_x_response::DstResult::Destination(json) => json,
|
||||
crate::rpc::aoba::share_x_response::DstResult::Error(err) => err,
|
||||
};
|
||||
}
|
||||
return "No Result".to_string();
|
||||
}
|
||||
let err = result.err().unwrap();
|
||||
let status = err.message();
|
||||
return format!("Failed to load config: {status}").to_string();
|
||||
});
|
||||
|
||||
let d = dst.cloned().unwrap_or("".to_string());
|
||||
|
||||
rsx! {
|
||||
"this is settings"
|
||||
div {
|
||||
pre {
|
||||
class: "codeSelect",
|
||||
"test {d}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
public class ShareXDestination
|
||||
{
|
||||
public string Version { get; set; } = "13.1.0";
|
||||
public string Version { get; set; } = "14.0.1";
|
||||
public string Name { get; set; } = "Aoba";
|
||||
public string DestinationType { get; set; } = "ImageUploader, TextUploader, FileUploader";
|
||||
public string RequestMethod { get; set; } = "POST";
|
||||
@@ -13,6 +13,6 @@ public class ShareXDestination
|
||||
public string FileFormName { get; set; } = "file";
|
||||
public string[] RegexList { get; set; } = ["([^/]+)/?$"];
|
||||
public string URL { get; set; } = "https://aoba.app$json:url$";
|
||||
public required string ThumbnailURL { get; set; }
|
||||
public required string DeletionURL { get; set; }
|
||||
public string? ThumbnailURL { get; set; }
|
||||
public string? DeletionURL { get; set; }
|
||||
}
|
||||
@@ -109,7 +109,6 @@ if (!app.Environment.IsDevelopment())
|
||||
}
|
||||
|
||||
app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true });
|
||||
app.UseHttpsRedirection();
|
||||
app.UseStaticFiles();
|
||||
app.UseRouting();
|
||||
|
||||
@@ -123,6 +122,7 @@ app.UseAuthorization();
|
||||
app.MapControllers();
|
||||
app.MapObserability();
|
||||
app.MapGrpcService<AobaRpcService>()
|
||||
.RequireAuthorization()
|
||||
.RequireCors("RPC");
|
||||
app.MapGrpcService<AobaAuthService>()
|
||||
.AllowAnonymous()
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
syntax = "proto3";
|
||||
import "google/protobuf/empty.proto";
|
||||
|
||||
option csharp_namespace = "Aoba.RPC";
|
||||
package aoba;
|
||||
|
||||
service AobaRpc {
|
||||
rpc GetMedia (Id) returns (MediaResponse);
|
||||
rpc DeleteMedia (Id) returns (Empty);
|
||||
rpc UpdateMedia (Empty) returns (Empty);
|
||||
rpc DeleteMedia (Id) returns (google.protobuf.Empty);
|
||||
rpc UpdateMedia (google.protobuf.Empty) returns (google.protobuf.Empty);
|
||||
rpc ListMedia(PageFilter) returns (ListResponse);
|
||||
rpc GetUser(Id) returns (UserResponse);
|
||||
rpc GetShareXDestination(google.protobuf.Empty) returns (ShareXResponse);
|
||||
}
|
||||
|
||||
message PageFilter {
|
||||
@@ -24,7 +26,7 @@ message Id {
|
||||
message MediaResponse {
|
||||
oneof result {
|
||||
MediaModel value = 1;
|
||||
Empty empty = 2;
|
||||
google.protobuf.Empty empty = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +46,7 @@ message Pagination {
|
||||
message UserResponse {
|
||||
oneof userResult {
|
||||
UserModel user = 1;
|
||||
Empty empty = 2;
|
||||
google.protobuf.Empty empty = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +57,6 @@ message UserModel {
|
||||
bool isAdmin = 4;
|
||||
}
|
||||
|
||||
message Empty {}
|
||||
|
||||
message MediaModel {
|
||||
Id id = 1;
|
||||
@@ -67,7 +68,7 @@ message MediaModel {
|
||||
Id owner = 7;
|
||||
}
|
||||
|
||||
enum MediaType{
|
||||
enum MediaType {
|
||||
Image = 0;
|
||||
Audio = 1;
|
||||
Video = 2;
|
||||
@@ -75,3 +76,10 @@ enum MediaType{
|
||||
Code = 4;
|
||||
Raw = 5;
|
||||
}
|
||||
|
||||
message ShareXResponse {
|
||||
oneof dstResult {
|
||||
string destination = 1;
|
||||
string error = 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
using AobaCore;
|
||||
using Aoba.RPC;
|
||||
|
||||
using Aoba.RPC;
|
||||
using AobaCore;
|
||||
|
||||
using AobaServer.Models;
|
||||
using AobaServer.Utils;
|
||||
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
using Grpc.Core;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using MongoDB.Bson.IO;
|
||||
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace AobaServer.Services;
|
||||
|
||||
public class AobaRpcService(AobaService aobaService) : AobaRpc.AobaRpcBase
|
||||
public class AobaRpcService(AobaService aobaService, AccountsService accountsService, AuthInfo authInfo) : AobaRpc.AobaRpcBase
|
||||
{
|
||||
public override async Task<MediaResponse> GetMedia(Id request, ServerCallContext context)
|
||||
{
|
||||
@@ -22,4 +32,29 @@ public class AobaRpcService(AobaService aobaService) : AobaRpc.AobaRpcBase
|
||||
return result.ToResponse();
|
||||
}
|
||||
|
||||
public override async Task<ShareXResponse> GetShareXDestination(Empty request, ServerCallContext context)
|
||||
{
|
||||
var userId = context.GetHttpContext().User.GetId();
|
||||
var user = await accountsService.GetUserAsync(userId, context.CancellationToken);
|
||||
if (user == null)
|
||||
return new ShareXResponse { Error = "User does not exist" };
|
||||
var token = user.GetToken(authInfo);
|
||||
var dest = new ShareXDestination
|
||||
{
|
||||
DeletionURL = string.Empty,
|
||||
ThumbnailURL = string.Empty,
|
||||
Headers = new()
|
||||
{
|
||||
{ "Authorization", $"Bearer {token}" }
|
||||
}
|
||||
};
|
||||
return new ShareXResponse
|
||||
{
|
||||
Destination = JsonSerializer.Serialize(dest, new JsonSerializerOptions
|
||||
{
|
||||
WriteIndented = true
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
using AobaServer.Models;
|
||||
|
||||
using Grpc.Core;
|
||||
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
|
||||
using MongoDB.Bson;
|
||||
@@ -37,4 +39,9 @@ public static class Extensions
|
||||
{
|
||||
return user.FindFirstValue(ClaimTypes.NameIdentifier).ToObjectId();
|
||||
}
|
||||
|
||||
public static ObjectId GetUserId(this ServerCallContext context)
|
||||
{
|
||||
return context.GetHttpContext().User.GetId();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using Aoba.RPC;
|
||||
|
||||
using MongoDB.Bson;
|
||||
using Google.Protobuf.WellKnownTypes;
|
||||
|
||||
namespace AobaServer.Utils;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user