refactor: refactor webui
This commit is contained in:
27
apps/webui/src/infra/auth/auth.provider.ts
Normal file
27
apps/webui/src/infra/auth/auth.provider.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { InjectionToken } from '@outposts/injection-js';
|
||||
import type { CheckAuthResultEventType } from 'oidc-client-rx';
|
||||
import { type Observable, map } from 'rxjs';
|
||||
import type { AuthMethodType } from './defs';
|
||||
|
||||
export abstract class AuthProvider {
|
||||
abstract authMethod: AuthMethodType;
|
||||
abstract checkAuthResultEvent$: Observable<CheckAuthResultEventType>;
|
||||
abstract isAuthenticated$: Observable<boolean>;
|
||||
abstract userData$: Observable<any>;
|
||||
abstract getAccessToken(): Observable<string | undefined>;
|
||||
abstract setup(): void;
|
||||
abstract autoLoginPartialRoutesGuard(): Observable<boolean>;
|
||||
getAuthHeaders(): Observable<Record<string, string>> {
|
||||
return this.getAccessToken().pipe(
|
||||
map((accessToken) =>
|
||||
accessToken
|
||||
? {
|
||||
Authorization: `Bearer ${accessToken}`,
|
||||
}
|
||||
: ({} as Record<string, string>)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const AUTH_PROVIDER = new InjectionToken<AuthProvider>('AUTH_PROVIDER');
|
||||
22
apps/webui/src/infra/auth/basic/basic-auth.provider.ts
Normal file
22
apps/webui/src/infra/auth/basic/basic-auth.provider.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { UnreachableError } from '@/infra/errors/common';
|
||||
import type { CheckAuthResultEventType } from 'oidc-client-rx';
|
||||
import { NEVER, type Observable, of } from 'rxjs';
|
||||
import { AuthProvider } from '../auth.provider';
|
||||
import { AUTH_METHOD } from '../defs';
|
||||
|
||||
export class BasicAuthProvider extends AuthProvider {
|
||||
authMethod = AUTH_METHOD.BASIC;
|
||||
isAuthenticated$ = of(true);
|
||||
userData$ = of({});
|
||||
checkAuthResultEvent$: Observable<CheckAuthResultEventType> = NEVER;
|
||||
|
||||
getAccessToken(): Observable<string | undefined> {
|
||||
return of(undefined);
|
||||
}
|
||||
|
||||
setup(): void {}
|
||||
|
||||
autoLoginPartialRoutesGuard(): Observable<boolean> {
|
||||
throw new UnreachableError('Basic auth should always be authenticated');
|
||||
}
|
||||
}
|
||||
1
apps/webui/src/infra/auth/basic/index.ts
Normal file
1
apps/webui/src/infra/auth/basic/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { BasicAuthProvider } from './basic-auth.provider';
|
||||
12
apps/webui/src/infra/auth/defs.ts
Normal file
12
apps/webui/src/infra/auth/defs.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ValueOf } from 'type-fest';
|
||||
|
||||
export const AUTH_METHOD = {
|
||||
BASIC: 'basic',
|
||||
OIDC: 'oidc',
|
||||
} as const;
|
||||
|
||||
export type AuthMethodType = ValueOf<typeof AUTH_METHOD>;
|
||||
|
||||
export function getAppAuthMethod(): AuthMethodType {
|
||||
return process.env.AUTH_TYPE as AuthMethodType;
|
||||
}
|
||||
35
apps/webui/src/infra/auth/oidc/config.ts
Normal file
35
apps/webui/src/infra/auth/oidc/config.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { LogLevel, type OpenIdConfiguration } from 'oidc-client-rx';
|
||||
|
||||
export function buildOidcConfig(): OpenIdConfiguration {
|
||||
const origin = window.location.origin;
|
||||
|
||||
const resource = process.env.OIDC_AUDIENCE!;
|
||||
|
||||
return {
|
||||
authority: process.env.OIDC_ISSUER!,
|
||||
redirectUrl: `${origin}/auth/oidc/callback`,
|
||||
postLogoutRedirectUri: `${origin}/`,
|
||||
clientId: process.env.OIDC_CLIENT_ID!,
|
||||
clientSecret: process.env.OIDC_CLIENT_SECRET,
|
||||
scope: process.env.OIDC_EXTRA_SCOPES
|
||||
? `openid profile email offline_access ${process.env.OIDC_EXTRA_SCOPES}`
|
||||
: 'openid profile email offline_access',
|
||||
triggerAuthorizationResultEvent: true,
|
||||
responseType: 'code',
|
||||
silentRenew: true,
|
||||
useRefreshToken: true,
|
||||
logLevel: LogLevel.None,
|
||||
autoUserInfo: !resource,
|
||||
renewUserInfoAfterTokenRenew: !resource,
|
||||
customParamsAuthRequest: {
|
||||
prompt: 'consent',
|
||||
resource,
|
||||
},
|
||||
customParamsRefreshTokenRequest: {
|
||||
resource,
|
||||
},
|
||||
customParamsCodeRequest: {
|
||||
resource,
|
||||
},
|
||||
};
|
||||
}
|
||||
2
apps/webui/src/infra/auth/oidc/index.ts
Normal file
2
apps/webui/src/infra/auth/oidc/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { buildOidcConfig } from './config';
|
||||
export { OidcAuthProvider } from './oidc-auth.provider';
|
||||
41
apps/webui/src/infra/auth/oidc/oidc-auth.provider.ts
Normal file
41
apps/webui/src/infra/auth/oidc/oidc-auth.provider.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { injectInjector } from '@/infra/di/inject';
|
||||
import { inject, runInInjectionContext } from '@outposts/injection-js';
|
||||
import {
|
||||
CHECK_AUTH_RESULT_EVENT,
|
||||
OidcSecurityService,
|
||||
autoLoginPartialRoutesGuard,
|
||||
} from 'oidc-client-rx';
|
||||
import { type Observable, map } from 'rxjs';
|
||||
import { AuthProvider } from '../auth.provider';
|
||||
import { AUTH_METHOD } from '../defs';
|
||||
|
||||
export class OidcAuthProvider extends AuthProvider {
|
||||
authMethod = AUTH_METHOD.OIDC;
|
||||
oidcSecurityService = inject(OidcSecurityService);
|
||||
checkAuthResultEvent$ = inject(CHECK_AUTH_RESULT_EVENT);
|
||||
injector = injectInjector();
|
||||
|
||||
setup() {
|
||||
this.oidcSecurityService.checkAuth().subscribe();
|
||||
}
|
||||
|
||||
get isAuthenticated$() {
|
||||
return this.oidcSecurityService.isAuthenticated$.pipe(
|
||||
map((s) => s.isAuthenticated)
|
||||
);
|
||||
}
|
||||
|
||||
get userData$() {
|
||||
return this.oidcSecurityService.userData$.pipe(map((s) => s.userData));
|
||||
}
|
||||
|
||||
getAccessToken(): Observable<string | undefined> {
|
||||
return this.oidcSecurityService.getAccessToken();
|
||||
}
|
||||
|
||||
autoLoginPartialRoutesGuard() {
|
||||
return runInInjectionContext(this.injector, () =>
|
||||
autoLoginPartialRoutesGuard()
|
||||
);
|
||||
}
|
||||
}
|
||||
16
apps/webui/src/infra/graphql/context.ts
Normal file
16
apps/webui/src/infra/graphql/context.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Injector, Provider } from '@outposts/injection-js';
|
||||
import { GraphQLService } from './graphql.service';
|
||||
|
||||
export function provideGraphql(): Provider[] {
|
||||
return [GraphQLService];
|
||||
}
|
||||
|
||||
export interface GraphQLContext {
|
||||
graphqlService: GraphQLService;
|
||||
}
|
||||
|
||||
export function graphqlContextFromInjector(injector: Injector): GraphQLContext {
|
||||
return {
|
||||
graphqlService: injector.get(GraphQLService),
|
||||
};
|
||||
}
|
||||
30
apps/webui/src/infra/graphql/graphql.service.ts
Normal file
30
apps/webui/src/infra/graphql/graphql.service.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';
|
||||
import { setContext } from '@apollo/client/link/context';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { AUTH_PROVIDER } from '../auth/auth.provider.ts';
|
||||
|
||||
@Injectable()
|
||||
export class GraphQLService {
|
||||
private authProvider = inject(AUTH_PROVIDER);
|
||||
|
||||
private apiLink = createHttpLink({
|
||||
uri: '/api/graphql',
|
||||
});
|
||||
|
||||
private authLink = setContext(async (_, { headers }) => {
|
||||
const authHeaders = await firstValueFrom(
|
||||
this.authProvider.getAuthHeaders()
|
||||
);
|
||||
return { headers: { ...headers, ...authHeaders } };
|
||||
});
|
||||
|
||||
_apollo = new ApolloClient({
|
||||
link: this.authLink.concat(this.apiLink),
|
||||
cache: new InMemoryCache(),
|
||||
});
|
||||
|
||||
query = this._apollo.query;
|
||||
mutate = this._apollo.mutate;
|
||||
watchQuery = this._apollo.watchQuery;
|
||||
}
|
||||
2
apps/webui/src/infra/graphql/index.ts
Normal file
2
apps/webui/src/infra/graphql/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { GraphQLService } from './graphql.service';
|
||||
export { provideGraphql } from './context';
|
||||
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
const MOBILE_BREAKPOINT = 768;
|
||||
|
||||
export function useIsMobile() {
|
||||
const [isMobile, setIsMobile] = React.useState<boolean | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`);
|
||||
const onChange = () => {
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
};
|
||||
mql.addEventListener('change', onChange);
|
||||
setIsMobile(window.innerWidth < MOBILE_BREAKPOINT);
|
||||
return () => mql.removeEventListener('change', onChange);
|
||||
}, []);
|
||||
|
||||
return !!isMobile;
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { OidcSecurityService } from 'oidc-client-rx';
|
||||
|
||||
@Injectable()
|
||||
export class HttpService {
|
||||
authService = inject(OidcSecurityService);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ProLinkProps } from '@/components/ui/pro-link';
|
||||
import type { ProLinkProps } from '@/views/components/ui/pro-link';
|
||||
import type { Injector } from '@outposts/injection-js';
|
||||
import type { LucideIcon } from 'lucide-react';
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { guardRouteIndexAsNotFound } from '@/components/layout/app-not-found';
|
||||
import type { RouteStateDataOption } from '@/infra/routes/traits';
|
||||
import { guardRouteIndexAsNotFound } from '@/views/components/layout/app-not-found';
|
||||
import { Outlet } from '@tanstack/react-router';
|
||||
|
||||
export interface BuildVirtualBranchRouteOptions {
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
import type { ClassValue } from 'clsx';
|
||||
import { clsx } from 'clsx';
|
||||
import { twMerge } from 'tailwind-merge';
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
Reference in New Issue
Block a user