Streamlined grpc auth

Added ShareX Destiation on client
This commit is contained in:
2025-05-21 22:07:57 -04:00
parent acd30750a9
commit 7061b4c313
14 changed files with 155 additions and 46 deletions

View File

@@ -21,3 +21,8 @@ input[type="text"] {
border-radius: 20px;
}
}
textarea {
min-height: 200px;
min-width: 500px;
}

View File

@@ -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;
}

View File

@@ -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();
}));

View File

@@ -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(),
}
}
}

View File

@@ -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> {}
}
};
}

View File

@@ -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;
}

View File

@@ -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() }
}
}
}

View File

@@ -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}"
}
}
}
}

View File

@@ -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; }
}

View File

@@ -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()

View File

@@ -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;
}
}

View File

@@ -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
})
};
}
}

View File

@@ -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();
}
}

View File

@@ -2,6 +2,7 @@
using Aoba.RPC;
using MongoDB.Bson;
using Google.Protobuf.WellKnownTypes;
namespace AobaServer.Utils;