계정 생성 API 가 아예 없었다(그동안 users 를 손으로 INSERT 했다). 로그인 화면은 있는데 그 뒤에 설 계정을 만들 방법이 제품에 없는 상태였다. - auth_service.signup: 가입 = **새 회사(테넌트) 1개 + 첫 계정 1개**. users.company_id 가 NOT NULL 이고 모든 도메인이 company 로 스코프돼서, 회사 없는 계정은 아무것도 못 만든다 - services/external/google_identity: 구글 ID 토큰의 서명·iss·만료에 더해 **aud(우리 client_id)와 email_verified 를 본다.** aud 검사가 빠지면 남의 앱에 발급된 '진짜' 구글 토큰으로 우리 계정에 들어온다 — 서명도 발급자도 전부 맞으므로 다른 검사로는 안 걸린다 - users.provider/provider_uid 추가, password NULL 허용, id 20→64자(google_<sub> 가 20자를 넘는다). provider 에 server_default 를 같이 준 이유: ORM default 는 raw INSERT(테스트 시드)에 안 먹어서 NOT NULL 컬럼이면 그 경로가 통째로 깨진다 - attempt_login: 소셜 계정을 먼저 끊는다. 안 끊으면 bcrypt 가 None 해시를 만나 500 이다 - 같은 이메일이라도 id/pw 계정과 구글 계정을 **잇지 않는다.** 이으면 계정 선점이다 — 남의 이메일로 먼저 만들어 둔 계정에 그 사람의 구글 로그인이 들어간다 → DECISIONS 1-5 - LoginPage 는 admin 과 공유라 selfServe 로 갈랐다. admin 은 가입 링크도 구글 버튼도 안 뜬다 (admin 라우터에 /signup 이 없어 404 가 난다) - GOOGLE_CLIENT_ID 는 루트 .env 한 곳. compose 가 VITE_GOOGLE_CLIENT_ID 로 흘려보낸다 — 두 곳에 적으면 백엔드 aud 대조와 화면 버튼이 조용히 갈라진다 ★ 이미 도는 DB 는 init.sql 을 다시 적용해야 한다(말미 ALTER 섹션). pytest: auth 13건 + 구글 토큰 검증 8건(진짜 RSA 서명으로 aud·iss·만료·email_verified·변조 거절 확인) 통과. 전체 527 passed / 8 failed(전부 기존 실패, 인증과 무관). tsc·eslint·vite build 통과.
459 lines
15 KiB
TypeScript
459 lines
15 KiB
TypeScript
/**
|
|
* Generated by orval v7.21.0 🍺
|
|
* Do not edit manually.
|
|
* Web4Ai API
|
|
* OpenAPI spec version: 0.1.0
|
|
*/
|
|
import {
|
|
useMutation,
|
|
useQuery
|
|
} from '@tanstack/react-query';
|
|
import type {
|
|
DataTag,
|
|
DefinedInitialDataOptions,
|
|
DefinedUseQueryResult,
|
|
MutationFunction,
|
|
QueryClient,
|
|
QueryFunction,
|
|
QueryKey,
|
|
UndefinedInitialDataOptions,
|
|
UseMutationOptions,
|
|
UseMutationResult,
|
|
UseQueryOptions,
|
|
UseQueryResult
|
|
} from '@tanstack/react-query';
|
|
|
|
import type {
|
|
HTTPValidationError,
|
|
ReqGoogleLogin,
|
|
ReqLogin,
|
|
ReqSignup,
|
|
ReqUpdateMe,
|
|
ResLogin,
|
|
ResMe,
|
|
ResRefreshToken
|
|
} from '.././model';
|
|
|
|
import { customFetch } from '../../mutator/custom-fetch';
|
|
|
|
|
|
type SecondParameter<T extends (...args: never) => unknown> = Parameters<T>[1];
|
|
|
|
|
|
|
|
/**
|
|
* id/pw 로 로그인하고 JWT 토큰을 발급한다.
|
|
* @summary 로그인
|
|
*/
|
|
export const login = (
|
|
reqLogin: ReqLogin,
|
|
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
|
) => {
|
|
|
|
|
|
return customFetch<ResLogin>(
|
|
{url: `/v1/auth/login`, method: 'POST',
|
|
headers: {'Content-Type': 'application/json', },
|
|
data: reqLogin, signal
|
|
},
|
|
options);
|
|
}
|
|
|
|
|
|
|
|
export const getLoginMutationOptions = <TError = void | HTTPValidationError,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof login>>, TError,{data: ReqLogin}, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
): UseMutationOptions<Awaited<ReturnType<typeof login>>, TError,{data: ReqLogin}, TContext> => {
|
|
|
|
const mutationKey = ['login'];
|
|
const {mutation: mutationOptions, request: requestOptions} = options ?
|
|
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
|
options
|
|
: {...options, mutation: {...options.mutation, mutationKey}}
|
|
: {mutation: { mutationKey, }, request: undefined};
|
|
|
|
|
|
|
|
|
|
const mutationFn: MutationFunction<Awaited<ReturnType<typeof login>>, {data: ReqLogin}> = (props) => {
|
|
const {data} = props ?? {};
|
|
|
|
return login(data,requestOptions)
|
|
}
|
|
|
|
|
|
|
|
|
|
return { mutationFn, ...mutationOptions }}
|
|
|
|
export type LoginMutationResult = NonNullable<Awaited<ReturnType<typeof login>>>
|
|
export type LoginMutationBody = ReqLogin
|
|
export type LoginMutationError = void | HTTPValidationError
|
|
|
|
/**
|
|
* @summary 로그인
|
|
*/
|
|
export const useLogin = <TError = void | HTTPValidationError,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof login>>, TError,{data: ReqLogin}, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
, queryClient?: QueryClient): UseMutationResult<
|
|
Awaited<ReturnType<typeof login>>,
|
|
TError,
|
|
{data: ReqLogin},
|
|
TContext
|
|
> => {
|
|
|
|
const mutationOptions = getLoginMutationOptions(options);
|
|
|
|
return useMutation(mutationOptions, queryClient);
|
|
}
|
|
/**
|
|
* id/pw 로 가입한다. 회사(테넌트) 1개가 함께 생기고, 성공하면 곧바로 로그인 토큰을 발급한다.
|
|
* @summary 회원가입
|
|
*/
|
|
export const signup = (
|
|
reqSignup: ReqSignup,
|
|
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
|
) => {
|
|
|
|
|
|
return customFetch<ResLogin>(
|
|
{url: `/v1/auth/signup`, method: 'POST',
|
|
headers: {'Content-Type': 'application/json', },
|
|
data: reqSignup, signal
|
|
},
|
|
options);
|
|
}
|
|
|
|
|
|
|
|
export const getSignupMutationOptions = <TError = void | HTTPValidationError,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof signup>>, TError,{data: ReqSignup}, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
): UseMutationOptions<Awaited<ReturnType<typeof signup>>, TError,{data: ReqSignup}, TContext> => {
|
|
|
|
const mutationKey = ['signup'];
|
|
const {mutation: mutationOptions, request: requestOptions} = options ?
|
|
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
|
options
|
|
: {...options, mutation: {...options.mutation, mutationKey}}
|
|
: {mutation: { mutationKey, }, request: undefined};
|
|
|
|
|
|
|
|
|
|
const mutationFn: MutationFunction<Awaited<ReturnType<typeof signup>>, {data: ReqSignup}> = (props) => {
|
|
const {data} = props ?? {};
|
|
|
|
return signup(data,requestOptions)
|
|
}
|
|
|
|
|
|
|
|
|
|
return { mutationFn, ...mutationOptions }}
|
|
|
|
export type SignupMutationResult = NonNullable<Awaited<ReturnType<typeof signup>>>
|
|
export type SignupMutationBody = ReqSignup
|
|
export type SignupMutationError = void | HTTPValidationError
|
|
|
|
/**
|
|
* @summary 회원가입
|
|
*/
|
|
export const useSignup = <TError = void | HTTPValidationError,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof signup>>, TError,{data: ReqSignup}, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
, queryClient?: QueryClient): UseMutationResult<
|
|
Awaited<ReturnType<typeof signup>>,
|
|
TError,
|
|
{data: ReqSignup},
|
|
TContext
|
|
> => {
|
|
|
|
const mutationOptions = getSignupMutationOptions(options);
|
|
|
|
return useMutation(mutationOptions, queryClient);
|
|
}
|
|
/**
|
|
* 구글 ID 토큰(GIS credential)을 검증하고 JWT 토큰을 발급한다. 처음 온 계정은 그 자리에서 만든다.
|
|
* @summary 구글 로그인
|
|
*/
|
|
export const googleLogin = (
|
|
reqGoogleLogin: ReqGoogleLogin,
|
|
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
|
) => {
|
|
|
|
|
|
return customFetch<ResLogin>(
|
|
{url: `/v1/auth/google`, method: 'POST',
|
|
headers: {'Content-Type': 'application/json', },
|
|
data: reqGoogleLogin, signal
|
|
},
|
|
options);
|
|
}
|
|
|
|
|
|
|
|
export const getGoogleLoginMutationOptions = <TError = void | HTTPValidationError,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof googleLogin>>, TError,{data: ReqGoogleLogin}, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
): UseMutationOptions<Awaited<ReturnType<typeof googleLogin>>, TError,{data: ReqGoogleLogin}, TContext> => {
|
|
|
|
const mutationKey = ['googleLogin'];
|
|
const {mutation: mutationOptions, request: requestOptions} = options ?
|
|
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
|
options
|
|
: {...options, mutation: {...options.mutation, mutationKey}}
|
|
: {mutation: { mutationKey, }, request: undefined};
|
|
|
|
|
|
|
|
|
|
const mutationFn: MutationFunction<Awaited<ReturnType<typeof googleLogin>>, {data: ReqGoogleLogin}> = (props) => {
|
|
const {data} = props ?? {};
|
|
|
|
return googleLogin(data,requestOptions)
|
|
}
|
|
|
|
|
|
|
|
|
|
return { mutationFn, ...mutationOptions }}
|
|
|
|
export type GoogleLoginMutationResult = NonNullable<Awaited<ReturnType<typeof googleLogin>>>
|
|
export type GoogleLoginMutationBody = ReqGoogleLogin
|
|
export type GoogleLoginMutationError = void | HTTPValidationError
|
|
|
|
/**
|
|
* @summary 구글 로그인
|
|
*/
|
|
export const useGoogleLogin = <TError = void | HTTPValidationError,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof googleLogin>>, TError,{data: ReqGoogleLogin}, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
, queryClient?: QueryClient): UseMutationResult<
|
|
Awaited<ReturnType<typeof googleLogin>>,
|
|
TError,
|
|
{data: ReqGoogleLogin},
|
|
TContext
|
|
> => {
|
|
|
|
const mutationOptions = getGoogleLoginMutationOptions(options);
|
|
|
|
return useMutation(mutationOptions, queryClient);
|
|
}
|
|
/**
|
|
* refresh 토큰으로 access 토큰을 재발급한다.
|
|
* @summary 액세스 토큰 갱신
|
|
*/
|
|
export const refreshToken = (
|
|
|
|
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
|
) => {
|
|
|
|
|
|
return customFetch<ResRefreshToken>(
|
|
{url: `/v1/auth/refresh_token`, method: 'POST', signal
|
|
},
|
|
options);
|
|
}
|
|
|
|
|
|
|
|
export const getRefreshTokenMutationOptions = <TError = void,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof refreshToken>>, TError,void, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
): UseMutationOptions<Awaited<ReturnType<typeof refreshToken>>, TError,void, TContext> => {
|
|
|
|
const mutationKey = ['refreshToken'];
|
|
const {mutation: mutationOptions, request: requestOptions} = options ?
|
|
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
|
options
|
|
: {...options, mutation: {...options.mutation, mutationKey}}
|
|
: {mutation: { mutationKey, }, request: undefined};
|
|
|
|
|
|
|
|
|
|
const mutationFn: MutationFunction<Awaited<ReturnType<typeof refreshToken>>, void> = () => {
|
|
|
|
|
|
return refreshToken(requestOptions)
|
|
}
|
|
|
|
|
|
|
|
|
|
return { mutationFn, ...mutationOptions }}
|
|
|
|
export type RefreshTokenMutationResult = NonNullable<Awaited<ReturnType<typeof refreshToken>>>
|
|
|
|
export type RefreshTokenMutationError = void
|
|
|
|
/**
|
|
* @summary 액세스 토큰 갱신
|
|
*/
|
|
export const useRefreshToken = <TError = void,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof refreshToken>>, TError,void, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
, queryClient?: QueryClient): UseMutationResult<
|
|
Awaited<ReturnType<typeof refreshToken>>,
|
|
TError,
|
|
void,
|
|
TContext
|
|
> => {
|
|
|
|
const mutationOptions = getRefreshTokenMutationOptions(options);
|
|
|
|
return useMutation(mutationOptions, queryClient);
|
|
}
|
|
/**
|
|
* 유효한 access 토큰이 있어야 호출 가능. 토큰의 유저+회사 정보를 반환한다.
|
|
* @summary 내 정보
|
|
*/
|
|
export const me = (
|
|
|
|
options?: SecondParameter<typeof customFetch>,signal?: AbortSignal
|
|
) => {
|
|
|
|
|
|
return customFetch<ResMe>(
|
|
{url: `/v1/auth/me`, method: 'GET', signal
|
|
},
|
|
options);
|
|
}
|
|
|
|
|
|
|
|
|
|
export const getMeQueryKey = () => {
|
|
return [
|
|
`/v1/auth/me`
|
|
] as const;
|
|
}
|
|
|
|
|
|
export const getMeQueryOptions = <TData = Awaited<ReturnType<typeof me>>, TError = void>( options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
|
) => {
|
|
|
|
const {query: queryOptions, request: requestOptions} = options ?? {};
|
|
|
|
const queryKey = queryOptions?.queryKey ?? getMeQueryKey();
|
|
|
|
|
|
|
|
const queryFn: QueryFunction<Awaited<ReturnType<typeof me>>> = ({ signal }) => me(requestOptions, signal);
|
|
|
|
|
|
|
|
|
|
|
|
return { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData> & { queryKey: DataTag<QueryKey, TData, TError> }
|
|
}
|
|
|
|
export type MeQueryResult = NonNullable<Awaited<ReturnType<typeof me>>>
|
|
export type MeQueryError = void
|
|
|
|
|
|
export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
|
options: { query:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>> & Pick<
|
|
DefinedInitialDataOptions<
|
|
Awaited<ReturnType<typeof me>>,
|
|
TError,
|
|
Awaited<ReturnType<typeof me>>
|
|
> , 'initialData'
|
|
>, request?: SecondParameter<typeof customFetch>}
|
|
, queryClient?: QueryClient
|
|
): DefinedUseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
|
export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
|
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>> & Pick<
|
|
UndefinedInitialDataOptions<
|
|
Awaited<ReturnType<typeof me>>,
|
|
TError,
|
|
Awaited<ReturnType<typeof me>>
|
|
> , 'initialData'
|
|
>, request?: SecondParameter<typeof customFetch>}
|
|
, queryClient?: QueryClient
|
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
|
export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
|
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
|
, queryClient?: QueryClient
|
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> }
|
|
/**
|
|
* @summary 내 정보
|
|
*/
|
|
|
|
export function useMe<TData = Awaited<ReturnType<typeof me>>, TError = void>(
|
|
options?: { query?:Partial<UseQueryOptions<Awaited<ReturnType<typeof me>>, TError, TData>>, request?: SecondParameter<typeof customFetch>}
|
|
, queryClient?: QueryClient
|
|
): UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> } {
|
|
|
|
const queryOptions = getMeQueryOptions(options)
|
|
|
|
const query = useQuery(queryOptions, queryClient) as UseQueryResult<TData, TError> & { queryKey: DataTag<QueryKey, TData, TError> };
|
|
|
|
query.queryKey = queryOptions.queryKey ;
|
|
|
|
return query;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* 본인 이름/이메일/연락처/비밀번호를 수정한다(권한·소속·ID 변경 불가).
|
|
* @summary 내 정보 수정
|
|
*/
|
|
export const updateMe = (
|
|
reqUpdateMe: ReqUpdateMe,
|
|
options?: SecondParameter<typeof customFetch>,) => {
|
|
|
|
|
|
return customFetch<ResMe>(
|
|
{url: `/v1/auth/me`, method: 'PATCH',
|
|
headers: {'Content-Type': 'application/json', },
|
|
data: reqUpdateMe
|
|
},
|
|
options);
|
|
}
|
|
|
|
|
|
|
|
export const getUpdateMeMutationOptions = <TError = void | HTTPValidationError,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMe>>, TError,{data: ReqUpdateMe}, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
): UseMutationOptions<Awaited<ReturnType<typeof updateMe>>, TError,{data: ReqUpdateMe}, TContext> => {
|
|
|
|
const mutationKey = ['updateMe'];
|
|
const {mutation: mutationOptions, request: requestOptions} = options ?
|
|
options.mutation && 'mutationKey' in options.mutation && options.mutation.mutationKey ?
|
|
options
|
|
: {...options, mutation: {...options.mutation, mutationKey}}
|
|
: {mutation: { mutationKey, }, request: undefined};
|
|
|
|
|
|
|
|
|
|
const mutationFn: MutationFunction<Awaited<ReturnType<typeof updateMe>>, {data: ReqUpdateMe}> = (props) => {
|
|
const {data} = props ?? {};
|
|
|
|
return updateMe(data,requestOptions)
|
|
}
|
|
|
|
|
|
|
|
|
|
return { mutationFn, ...mutationOptions }}
|
|
|
|
export type UpdateMeMutationResult = NonNullable<Awaited<ReturnType<typeof updateMe>>>
|
|
export type UpdateMeMutationBody = ReqUpdateMe
|
|
export type UpdateMeMutationError = void | HTTPValidationError
|
|
|
|
/**
|
|
* @summary 내 정보 수정
|
|
*/
|
|
export const useUpdateMe = <TError = void | HTTPValidationError,
|
|
TContext = unknown>(options?: { mutation?:UseMutationOptions<Awaited<ReturnType<typeof updateMe>>, TError,{data: ReqUpdateMe}, TContext>, request?: SecondParameter<typeof customFetch>}
|
|
, queryClient?: QueryClient): UseMutationResult<
|
|
Awaited<ReturnType<typeof updateMe>>,
|
|
TError,
|
|
{data: ReqUpdateMe},
|
|
TContext
|
|
> => {
|
|
|
|
const mutationOptions = getUpdateMeMutationOptions(options);
|
|
|
|
return useMutation(mutationOptions, queryClient);
|
|
}
|
|
|