refactor: remove loco-rs deps
This commit is contained in:
52
apps/recorder/src/web/controller/__root.tsx
Normal file
52
apps/recorder/src/web/controller/__root.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import type { Injector } from '@outposts/injection-js';
|
||||
import {
|
||||
// Link,
|
||||
Outlet,
|
||||
createRootRouteWithContext,
|
||||
} from '@tanstack/react-router';
|
||||
import { TanStackRouterDevtools } from '@tanstack/router-devtools';
|
||||
import type { OidcSecurityService } from 'oidc-client-rx';
|
||||
|
||||
export type RouterContext =
|
||||
| {
|
||||
isAuthenticated: false;
|
||||
injector: Injector;
|
||||
oidcSecurityService: OidcSecurityService;
|
||||
}
|
||||
| {
|
||||
isAuthenticated: true;
|
||||
injector?: Injector;
|
||||
oidcSecurityService?: OidcSecurityService;
|
||||
};
|
||||
|
||||
export const Route = createRootRouteWithContext<RouterContext>()({
|
||||
component: RootComponent,
|
||||
});
|
||||
|
||||
function RootComponent() {
|
||||
return (
|
||||
<>
|
||||
{/* <div className="flex gap-2 p-2 text-lg ">
|
||||
<Link
|
||||
to="/"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
>
|
||||
Home
|
||||
</Link>{' '}
|
||||
<Link
|
||||
to="/graphql"
|
||||
activeProps={{
|
||||
className: 'font-bold',
|
||||
}}
|
||||
>
|
||||
GraphQL
|
||||
</Link>
|
||||
</div> */}
|
||||
{/* <hr /> */}
|
||||
<Outlet />
|
||||
<TanStackRouterDevtools position="bottom-right" />
|
||||
</>
|
||||
);
|
||||
}
|
||||
50
apps/recorder/src/web/controller/core.rs
Normal file
50
apps/recorder/src/web/controller/core.rs
Normal file
@@ -0,0 +1,50 @@
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use axum::Router;
|
||||
|
||||
use crate::app::AppContext;
|
||||
|
||||
pub trait ControllerTrait: Sized {
|
||||
fn apply_to(self, router: Router<Arc<AppContext>>) -> Router<Arc<AppContext>>;
|
||||
}
|
||||
|
||||
pub struct PrefixController {
|
||||
prefix: Cow<'static, str>,
|
||||
router: Router<Arc<AppContext>>,
|
||||
}
|
||||
|
||||
impl PrefixController {
|
||||
pub fn new(prefix: impl Into<Cow<'static, str>>, router: Router<Arc<AppContext>>) -> Self {
|
||||
Self {
|
||||
prefix: prefix.into(),
|
||||
router,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ControllerTrait for PrefixController {
|
||||
fn apply_to(self, router: Router<Arc<AppContext>>) -> Router<Arc<AppContext>> {
|
||||
router.nest(&self.prefix, self.router)
|
||||
}
|
||||
}
|
||||
|
||||
pub enum Controller {
|
||||
Prefix(PrefixController),
|
||||
}
|
||||
|
||||
impl Controller {
|
||||
pub fn from_prefix(
|
||||
prefix: impl Into<Cow<'static, str>>,
|
||||
router: Router<Arc<AppContext>>,
|
||||
) -> Self {
|
||||
Self::Prefix(PrefixController::new(prefix, router))
|
||||
}
|
||||
}
|
||||
|
||||
impl ControllerTrait for Controller {
|
||||
fn apply_to(self, router: Router<Arc<AppContext>>) -> Router<Arc<AppContext>> {
|
||||
match self {
|
||||
Self::Prefix(p) => p.apply_to(router),
|
||||
}
|
||||
}
|
||||
}
|
||||
36
apps/recorder/src/web/controller/graphql/index.tsx
Normal file
36
apps/recorder/src/web/controller/graphql/index.tsx
Normal file
@@ -0,0 +1,36 @@
|
||||
import { type Fetcher, createGraphiQLFetcher } from '@graphiql/toolkit';
|
||||
import { createFileRoute } from '@tanstack/react-router';
|
||||
import GraphiQL from 'graphiql';
|
||||
import { useMemo } from 'react';
|
||||
import { beforeLoadGuard } from '../../../auth/guard';
|
||||
import 'graphiql/graphiql.css';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { useAuth } from '../../../auth/hooks';
|
||||
|
||||
export const Route = createFileRoute('/graphql/')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: beforeLoadGuard,
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const { oidcSecurityService } = useAuth();
|
||||
|
||||
const fetcher = useMemo(
|
||||
(): Fetcher => async (props) => {
|
||||
const accessToken = oidcSecurityService
|
||||
? await firstValueFrom(oidcSecurityService.getAccessToken())
|
||||
: undefined;
|
||||
return createGraphiQLFetcher({
|
||||
url: '/api/graphql',
|
||||
headers: accessToken
|
||||
? {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
}
|
||||
: undefined,
|
||||
})(props);
|
||||
},
|
||||
[oidcSecurityService]
|
||||
);
|
||||
|
||||
return <GraphiQL fetcher={fetcher} className="h-svh" />;
|
||||
}
|
||||
33
apps/recorder/src/web/controller/graphql/mod.rs
Normal file
33
apps/recorder/src/web/controller/graphql/mod.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
|
||||
use axum::{Extension, Router, extract::State, middleware::from_fn_with_state, routing::post};
|
||||
|
||||
use super::core::Controller;
|
||||
use crate::{
|
||||
app::AppContext,
|
||||
auth::{AuthUserInfo, header_www_authenticate_middleware},
|
||||
errors::RResult,
|
||||
};
|
||||
|
||||
pub const CONTROLLER_PREFIX: &str = "/api/graphql";
|
||||
|
||||
async fn graphql_handler(
|
||||
State(ctx): State<Arc<AppContext>>,
|
||||
Extension(auth_user_info): Extension<AuthUserInfo>,
|
||||
req: GraphQLRequest,
|
||||
) -> GraphQLResponse {
|
||||
let graphql_service = &ctx.graphql;
|
||||
|
||||
let mut req = req.into_inner();
|
||||
req = req.data(auth_user_info);
|
||||
|
||||
graphql_service.schema.execute(req).await.into()
|
||||
}
|
||||
|
||||
pub async fn create(ctx: Arc<AppContext>) -> RResult<Controller> {
|
||||
let router = Router::<Arc<AppContext>>::new()
|
||||
.route("/", post(graphql_handler))
|
||||
.layer(from_fn_with_state(ctx, header_www_authenticate_middleware));
|
||||
Ok(Controller::from_prefix(CONTROLLER_PREFIX, router))
|
||||
}
|
||||
9
apps/recorder/src/web/controller/index.tsx
Normal file
9
apps/recorder/src/web/controller/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { createFileRoute } from '@tanstack/react-router'
|
||||
|
||||
export const Route = createFileRoute('/')({
|
||||
component: RouteComponent,
|
||||
})
|
||||
|
||||
function RouteComponent() {
|
||||
return <div>Hello to playground!</div>
|
||||
}
|
||||
5
apps/recorder/src/web/controller/mod.rs
Normal file
5
apps/recorder/src/web/controller/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
pub mod core;
|
||||
pub mod graphql;
|
||||
pub mod oidc;
|
||||
|
||||
pub use core::{Controller, ControllerTrait, PrefixController};
|
||||
32
apps/recorder/src/web/controller/oidc/callback.tsx
Normal file
32
apps/recorder/src/web/controller/oidc/callback.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { createFileRoute, redirect } from '@tanstack/react-router';
|
||||
import { EventTypes } from 'oidc-client-rx';
|
||||
import { useAuth } from '../../../auth/hooks';
|
||||
|
||||
export const Route = createFileRoute('/oidc/callback')({
|
||||
component: RouteComponent,
|
||||
beforeLoad: ({ context }) => {
|
||||
if (!context.oidcSecurityService) {
|
||||
throw redirect({
|
||||
to: '/',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
function RouteComponent() {
|
||||
const auth = useAuth();
|
||||
|
||||
if (!auth.checkAuthResultEvent) {
|
||||
return <div>Loading...</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
OpenID Connect Auth Callback:{' '}
|
||||
{auth.checkAuthResultEvent?.type ===
|
||||
EventTypes.CheckingAuthFinishedWithError
|
||||
? auth.checkAuthResultEvent.value
|
||||
: 'success'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
79
apps/recorder/src/web/controller/oidc/mod.rs
Normal file
79
apps/recorder/src/web/controller/oidc/mod.rs
Normal file
@@ -0,0 +1,79 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use axum::{
|
||||
Json, Router,
|
||||
extract::{Query, State},
|
||||
http::request::Parts,
|
||||
routing::get,
|
||||
};
|
||||
|
||||
use super::core::Controller;
|
||||
use crate::{
|
||||
app::AppContext,
|
||||
auth::{
|
||||
AuthError, AuthService, AuthServiceTrait,
|
||||
oidc::{OidcAuthCallbackPayload, OidcAuthCallbackQuery, OidcAuthRequest},
|
||||
},
|
||||
errors::RResult,
|
||||
extract::http::ForwardedRelatedInfo,
|
||||
models::auth::AuthType,
|
||||
};
|
||||
|
||||
pub const CONTROLLER_PREFIX: &str = "/api/oidc";
|
||||
|
||||
async fn oidc_callback(
|
||||
State(ctx): State<Arc<AppContext>>,
|
||||
Query(query): Query<OidcAuthCallbackQuery>,
|
||||
) -> Result<Json<OidcAuthCallbackPayload>, AuthError> {
|
||||
let auth_service = &ctx.auth;
|
||||
if let AuthService::Oidc(oidc_auth_service) = auth_service {
|
||||
let response = oidc_auth_service
|
||||
.extract_authorization_request_callback(query)
|
||||
.await?;
|
||||
Ok(Json(response))
|
||||
} else {
|
||||
Err(AuthError::NotSupportAuthMethod {
|
||||
supported: vec![auth_service.auth_type()],
|
||||
current: AuthType::Oidc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn oidc_auth(
|
||||
State(ctx): State<Arc<AppContext>>,
|
||||
parts: Parts,
|
||||
) -> Result<Json<OidcAuthRequest>, AuthError> {
|
||||
let auth_service = &ctx.auth;
|
||||
if let AuthService::Oidc(oidc_auth_service) = auth_service {
|
||||
let mut redirect_uri = ForwardedRelatedInfo::from_request_parts(&parts)
|
||||
.resolved_origin()
|
||||
.ok_or_else(|| AuthError::OidcRequestRedirectUriError(url::ParseError::EmptyHost))?;
|
||||
|
||||
redirect_uri.set_path(&format!("{CONTROLLER_PREFIX}/callback"));
|
||||
|
||||
let auth_request = oidc_auth_service
|
||||
.build_authorization_request(redirect_uri.as_str())
|
||||
.await?;
|
||||
|
||||
{
|
||||
oidc_auth_service
|
||||
.store_authorization_request(auth_request.clone())
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(Json(auth_request))
|
||||
} else {
|
||||
Err(AuthError::NotSupportAuthMethod {
|
||||
supported: vec![auth_service.auth_type()],
|
||||
current: AuthType::Oidc,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create(_context: Arc<AppContext>) -> RResult<Controller> {
|
||||
let router = Router::<Arc<AppContext>>::new()
|
||||
.route("/auth", get(oidc_auth))
|
||||
.route("/callback", get(oidc_callback));
|
||||
|
||||
Ok(Controller::from_prefix(CONTROLLER_PREFIX, router))
|
||||
}
|
||||
Reference in New Issue
Block a user