✨ User login
This commit is contained in:
parent
66255e1879
commit
6aced3ddf7
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"semi": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
40
README.md
40
README.md
@ -1,40 +0,0 @@
|
|||||||
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/pages/api-reference/create-next-app).
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
First, run the development server:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
npm run dev
|
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
# or
|
|
||||||
pnpm dev
|
|
||||||
# or
|
|
||||||
bun dev
|
|
||||||
```
|
|
||||||
|
|
||||||
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
||||||
|
|
||||||
You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.
|
|
||||||
|
|
||||||
[API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
|
|
||||||
|
|
||||||
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/pages/building-your-application/routing/api-routes) instead of React pages.
|
|
||||||
|
|
||||||
This project uses [`next/font`](https://nextjs.org/docs/pages/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.
|
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js, take a look at the following resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn-pages-router) - an interactive Next.js tutorial.
|
|
||||||
|
|
||||||
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!
|
|
||||||
|
|
||||||
## Deploy on Vercel
|
|
||||||
|
|
||||||
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
|
|
||||||
|
|
||||||
Check out our [Next.js deployment documentation](https://nextjs.org/docs/pages/building-your-application/deploying) for more details.
|
|
@ -13,9 +13,13 @@
|
|||||||
"@emotion/styled": "^11.14.0",
|
"@emotion/styled": "^11.14.0",
|
||||||
"@mui/icons-material": "^6.3.0",
|
"@mui/icons-material": "^6.3.0",
|
||||||
"@mui/material": "^6.3.0",
|
"@mui/material": "^6.3.0",
|
||||||
|
"axios": "^1.7.9",
|
||||||
|
"axios-case-converter": "^1.1.1",
|
||||||
|
"cookies-next": "^5.0.2",
|
||||||
"next": "15.1.3",
|
"next": "15.1.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0"
|
"react-dom": "^19.0.0",
|
||||||
|
"react-hook-form": "^7.54.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"typescript": "^5",
|
"typescript": "^5",
|
||||||
|
@ -1,24 +1,23 @@
|
|||||||
import { AppBar, Box, IconButton, Toolbar, Typography } from "@mui/material";
|
import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material'
|
||||||
import MenuIcon from "@mui/icons-material/Menu";
|
import MenuIcon from '@mui/icons-material/Menu'
|
||||||
import AccountCircle from "@mui/icons-material/AccountCircle";
|
import AccountCircle from '@mui/icons-material/AccountCircle'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
export function CapAppBar() {
|
export function CapAppBar() {
|
||||||
return (
|
return (
|
||||||
<Box sx={{ flexGrow: 1 }}>
|
<Box sx={{ flexGrow: 1 }}>
|
||||||
<AppBar position="static" elevation={0} color="transparent">
|
<AppBar position="static" elevation={0} color="transparent">
|
||||||
<Toolbar>
|
<Toolbar>
|
||||||
<IconButton
|
<IconButton size="large" edge="start" color="inherit" aria-label="menu" sx={{ mr: 2 }}>
|
||||||
size="large"
|
|
||||||
edge="start"
|
|
||||||
color="inherit"
|
|
||||||
aria-label="menu"
|
|
||||||
sx={{ mr: 2 }}
|
|
||||||
>
|
|
||||||
<MenuIcon />
|
<MenuIcon />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
<Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
|
<Link href="/" passHref style={{ flexGrow: 1 }}>
|
||||||
|
<Typography variant="h6" component="div">
|
||||||
Capital
|
Capital
|
||||||
</Typography>
|
</Typography>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<Link href="/auth/login" passHref>
|
||||||
<IconButton
|
<IconButton
|
||||||
size="large"
|
size="large"
|
||||||
aria-label="account of current user"
|
aria-label="account of current user"
|
||||||
@ -28,8 +27,9 @@ export function CapAppBar() {
|
|||||||
>
|
>
|
||||||
<AccountCircle />
|
<AccountCircle />
|
||||||
</IconButton>
|
</IconButton>
|
||||||
|
</Link>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
</Box>
|
</Box>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
83
src/components/auth/SnLoginCheckpoint.tsx
Normal file
83
src/components/auth/SnLoginCheckpoint.tsx
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { SnAuthFactor, SnAuthResult, SnAuthTicket } from '@/services/auth'
|
||||||
|
import { sni } from '@/services/network'
|
||||||
|
import { ArrowForward } from '@mui/icons-material'
|
||||||
|
import { Collapse, Alert, Box, TextField, Button } from '@mui/material'
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { useForm } from 'react-hook-form'
|
||||||
|
|
||||||
|
import ErrorIcon from '@mui/icons-material/Error'
|
||||||
|
import { setCookie } from 'cookies-next/client'
|
||||||
|
|
||||||
|
export interface SnLoginCheckpointForm {
|
||||||
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SnLoginCheckpoint({
|
||||||
|
ticket,
|
||||||
|
factor,
|
||||||
|
onNext,
|
||||||
|
}: {
|
||||||
|
ticket: SnAuthTicket
|
||||||
|
factor: SnAuthFactor
|
||||||
|
onNext: (val: SnAuthTicket, done: boolean) => void
|
||||||
|
}) {
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const { handleSubmit, register } = useForm<SnLoginCheckpointForm>()
|
||||||
|
|
||||||
|
async function onSubmit(data: any) {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const resp = await sni.patch<SnAuthResult>('/cgi/id/auth', {
|
||||||
|
ticket_id: ticket.id,
|
||||||
|
factor_id: factor.id,
|
||||||
|
code: data.password,
|
||||||
|
})
|
||||||
|
|
||||||
|
if (resp.data.isFinished) {
|
||||||
|
const tokenResp = await sni.post('/cgi/id/auth/token', {
|
||||||
|
grant_type: 'grant_token',
|
||||||
|
code: resp.data.ticket.grantToken!,
|
||||||
|
})
|
||||||
|
const atk: string = tokenResp.data['accessToken']
|
||||||
|
const rtk: string = tokenResp.data['refreshToken']
|
||||||
|
setCookie('nex_user_atk', atk, { path: '/', maxAge: 2592000 })
|
||||||
|
setCookie('nex_user_rtk', rtk, { path: '/', maxAge: 2592000 })
|
||||||
|
console.log('[Authenticator] User has been logged in. Result atk: ', atk)
|
||||||
|
}
|
||||||
|
|
||||||
|
onNext(resp.data.ticket, resp.data.isFinished)
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.toString())
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Collapse in={!!error} sx={{ width: 320 }}>
|
||||||
|
<Alert sx={{ mb: 4 }} icon={<ErrorIcon fontSize="inherit" />} severity="error">
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', width: 320, gap: 2, textAlign: 'center' }}>
|
||||||
|
<TextField
|
||||||
|
label={factor.type == 0 ? 'Password' : 'Verification code'}
|
||||||
|
type="password"
|
||||||
|
{...register('password', { required: true })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button variant="contained" endIcon={<ArrowForward />} disabled={loading} type="submit">
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
68
src/components/auth/SnLoginRouter.tsx
Normal file
68
src/components/auth/SnLoginRouter.tsx
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { SnAuthFactor, SnAuthTicket } from '@/services/auth'
|
||||||
|
import { sni } from '@/services/network'
|
||||||
|
import { Collapse, Alert, Box, Button, Typography, ButtonGroup } from '@mui/material'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
import ErrorIcon from '@mui/icons-material/Error'
|
||||||
|
import PasswordIcon from '@mui/icons-material/Password'
|
||||||
|
import EmailIcon from '@mui/icons-material/Email'
|
||||||
|
|
||||||
|
export function SnLoginRouter({
|
||||||
|
ticket,
|
||||||
|
factorList,
|
||||||
|
onNext,
|
||||||
|
}: {
|
||||||
|
ticket: SnAuthTicket
|
||||||
|
factorList: SnAuthFactor[]
|
||||||
|
onNext: (val: SnAuthFactor) => void
|
||||||
|
}) {
|
||||||
|
const factorTypeIcons = [<PasswordIcon />, <EmailIcon />]
|
||||||
|
const factorTypeLabels = ['Password', 'Email verification code']
|
||||||
|
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
|
async function onSubmit(factor: SnAuthFactor) {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
await sni.post('/cgi/id/auth/factors/' + factor.id)
|
||||||
|
onNext(factor)
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.toString())
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Collapse in={!!error} sx={{ width: 320 }}>
|
||||||
|
<Alert sx={{ mb: 4 }} icon={<ErrorIcon fontSize="inherit" />} severity="error">
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', width: 320, gap: 2, textAlign: 'center' }}>
|
||||||
|
<ButtonGroup orientation="vertical" aria-label="Vertical button group">
|
||||||
|
{factorList.map((factor) => (
|
||||||
|
<Button
|
||||||
|
sx={{ py: 1 }}
|
||||||
|
key={factor.id}
|
||||||
|
onClick={() => onSubmit(factor)}
|
||||||
|
disabled={loading || ticket.factorTrail?.includes(factor.id)}
|
||||||
|
startIcon={factorTypeIcons[factor.type]}
|
||||||
|
>
|
||||||
|
{factorTypeLabels[factor.type]}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</ButtonGroup>
|
||||||
|
|
||||||
|
<Typography variant="caption" sx={{ opacity: 0.75, mx: 2 }}>
|
||||||
|
{ticket.stepRemain} step(s) left
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
67
src/components/auth/SnLoginStart.tsx
Normal file
67
src/components/auth/SnLoginStart.tsx
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
import { sni } from '@/services/network'
|
||||||
|
import { ArrowForward } from '@mui/icons-material'
|
||||||
|
import { Alert, Box, Button, Collapse, Link, TextField, Typography } from '@mui/material'
|
||||||
|
import { SnAuthFactor, SnAuthResult, SnAuthTicket } from '@/services/auth'
|
||||||
|
import { useForm } from 'react-hook-form'
|
||||||
|
|
||||||
|
import ErrorIcon from '@mui/icons-material/Error'
|
||||||
|
|
||||||
|
export type SnLoginStartForm = {
|
||||||
|
username: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SnLoginStart({ onNext }: { onNext: (val: SnAuthTicket, fcs: SnAuthFactor[]) => void }) {
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [loading, setLoading] = useState<boolean>(false)
|
||||||
|
|
||||||
|
const { handleSubmit, register } = useForm<SnLoginStartForm>()
|
||||||
|
|
||||||
|
async function onSubmit(data: any) {
|
||||||
|
try {
|
||||||
|
setLoading(true)
|
||||||
|
const resp = await sni.post<SnAuthResult>('/cgi/id/auth', data)
|
||||||
|
const factorResp = await sni.get<SnAuthFactor[]>('/cgi/id/auth/factors', {
|
||||||
|
params: {
|
||||||
|
ticketId: resp.data.ticket.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
onNext(resp.data.ticket, factorResp.data)
|
||||||
|
} catch (err: any) {
|
||||||
|
setError(err.toString())
|
||||||
|
} finally {
|
||||||
|
setLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Collapse in={!!error} sx={{ width: 320 }}>
|
||||||
|
<Alert sx={{ mb: 4 }} icon={<ErrorIcon fontSize="inherit" />} severity="error">
|
||||||
|
{error}
|
||||||
|
</Alert>
|
||||||
|
</Collapse>
|
||||||
|
|
||||||
|
<form onSubmit={handleSubmit(onSubmit)}>
|
||||||
|
<Box sx={{ display: 'flex', flexDirection: 'column', width: 320, gap: 2, textAlign: 'center' }}>
|
||||||
|
<TextField
|
||||||
|
label="Username"
|
||||||
|
helperText="You can also use email address and phone number"
|
||||||
|
{...register('username', { required: true })}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button variant="contained" endIcon={<ArrowForward />} disabled={loading} type="submit">
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Typography variant="caption" sx={{ opacity: 0.75, mx: 2 }}>
|
||||||
|
By continuing means you agree to our <Link href="#">Terms of Service</Link> and{' '}
|
||||||
|
<Link href="#">Privacy Policy</Link>
|
||||||
|
</Typography>
|
||||||
|
</Box>
|
||||||
|
</form>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -1,26 +1,26 @@
|
|||||||
import "@/styles/globals.css";
|
import '@/styles/globals.css'
|
||||||
import type { AppProps } from "next/app";
|
import type { AppProps } from 'next/app'
|
||||||
import { createTheme, CssBaseline, ThemeProvider } from "@mui/material";
|
import { Box, createTheme, CssBaseline, ThemeProvider } from '@mui/material'
|
||||||
import { Roboto } from "next/font/google";
|
import { Roboto } from 'next/font/google'
|
||||||
import { CapAppBar } from "@/components/CapAppBar";
|
import { CapAppBar } from '@/components/CapAppBar'
|
||||||
|
|
||||||
const fontRoboto = Roboto({
|
const fontRoboto = Roboto({
|
||||||
subsets: ["latin"],
|
subsets: ['latin'],
|
||||||
weight: ["400", "500", "700"],
|
weight: ['400', '500', '700'],
|
||||||
display: "swap",
|
display: 'swap',
|
||||||
});
|
})
|
||||||
|
|
||||||
const siteTheme = createTheme({
|
const siteTheme = createTheme({
|
||||||
palette: {
|
palette: {
|
||||||
mode: "light",
|
mode: 'light',
|
||||||
primary: {
|
primary: {
|
||||||
main: "#3949ab",
|
main: '#3949ab',
|
||||||
},
|
},
|
||||||
secondary: {
|
secondary: {
|
||||||
main: "#1e88e5",
|
main: '#1e88e5',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
|
|
||||||
export default function App({ Component, pageProps }: AppProps) {
|
export default function App({ Component, pageProps }: AppProps) {
|
||||||
return (
|
return (
|
||||||
@ -35,8 +35,10 @@ export default function App({ Component, pageProps }: AppProps) {
|
|||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
|
|
||||||
<CapAppBar />
|
<CapAppBar />
|
||||||
|
<Box sx={{ height: 'calc(100vh - 64px)' }}>
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
|
</Box>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</>
|
</>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Html, Head, Main, NextScript } from "next/document";
|
import { Html, Head, Main, NextScript } from 'next/document'
|
||||||
|
|
||||||
export default function Document() {
|
export default function Document() {
|
||||||
return (
|
return (
|
||||||
@ -9,5 +9,5 @@ export default function Document() {
|
|||||||
<NextScript />
|
<NextScript />
|
||||||
</body>
|
</body>
|
||||||
</Html>
|
</Html>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
79
src/pages/auth/login.tsx
Normal file
79
src/pages/auth/login.tsx
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
import { SnLoginCheckpoint } from '@/components/auth/SnLoginCheckpoint'
|
||||||
|
import { SnLoginRouter } from '@/components/auth/SnLoginRouter'
|
||||||
|
import { SnLoginStart } from '@/components/auth/SnLoginStart'
|
||||||
|
import { SnAuthFactor, SnAuthTicket } from '@/services/auth'
|
||||||
|
import { Box, Container, Typography } from '@mui/material'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export default function Login() {
|
||||||
|
const [period, setPeriod] = useState<number>(0)
|
||||||
|
const [ticket, setTicket] = useState<SnAuthTicket | null>(null)
|
||||||
|
const [factorList, setFactorList] = useState<SnAuthFactor[]>([])
|
||||||
|
const [factor, setFactor] = useState<SnAuthFactor | null>(null)
|
||||||
|
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
function renderForm() {
|
||||||
|
switch (period) {
|
||||||
|
case 1:
|
||||||
|
return (
|
||||||
|
<SnLoginRouter
|
||||||
|
ticket={ticket!}
|
||||||
|
factorList={factorList}
|
||||||
|
onNext={(val) => {
|
||||||
|
setPeriod(period + 1)
|
||||||
|
setFactor(val)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
case 2:
|
||||||
|
return (
|
||||||
|
<SnLoginCheckpoint
|
||||||
|
ticket={ticket!}
|
||||||
|
factor={factor!}
|
||||||
|
onNext={(val, done) => {
|
||||||
|
if (!done) {
|
||||||
|
setTicket(val)
|
||||||
|
setPeriod(1)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
router.push('/')
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return (
|
||||||
|
<SnLoginStart
|
||||||
|
onNext={(val, fcs) => {
|
||||||
|
setPeriod(period + 1)
|
||||||
|
setTicket(val)
|
||||||
|
setFactorList(fcs)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container
|
||||||
|
sx={{
|
||||||
|
display: 'grid',
|
||||||
|
placeItems: 'center',
|
||||||
|
height: '100%',
|
||||||
|
textAlign: 'center',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Box>
|
||||||
|
<Typography variant="h5" component="h1">
|
||||||
|
Login
|
||||||
|
</Typography>
|
||||||
|
<Typography variant="subtitle2" component="h2">
|
||||||
|
Login via Solarpass
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<Box sx={{ mt: 3 }}>{renderForm()}</Box>
|
||||||
|
</Box>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { Container, Typography } from "@mui/material";
|
import { Container, Typography } from '@mui/material'
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
@ -10,5 +10,5 @@ export default function Home() {
|
|||||||
</Typography>
|
</Typography>
|
||||||
</Container>
|
</Container>
|
||||||
</main>
|
</main>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
|
34
src/services/auth.ts
Normal file
34
src/services/auth.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
export interface SnAuthResult {
|
||||||
|
isFinished: boolean
|
||||||
|
ticket: SnAuthTicket
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SnAuthTicket {
|
||||||
|
id: number
|
||||||
|
createdAt: Date
|
||||||
|
updatedAt: Date
|
||||||
|
deletedAt?: Date | null
|
||||||
|
stepRemain: number
|
||||||
|
grantToken?: string | null
|
||||||
|
accessToken?: string | null
|
||||||
|
refreshToken?: string | null
|
||||||
|
ipAddress: string
|
||||||
|
location: string
|
||||||
|
userAgent: string
|
||||||
|
expiredAt?: Date | null
|
||||||
|
lastGrantAt?: Date | null
|
||||||
|
availableAt?: Date | null
|
||||||
|
nonce?: string | null
|
||||||
|
accountId?: number | null
|
||||||
|
factorTrail: number[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SnAuthFactor {
|
||||||
|
id: number
|
||||||
|
createdAt: Date
|
||||||
|
updatedAt: Date
|
||||||
|
deletedAt?: Date | null
|
||||||
|
type: number
|
||||||
|
config?: Record<string, any> | null
|
||||||
|
accountId?: number | null
|
||||||
|
}
|
12
src/services/network.ts
Normal file
12
src/services/network.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import applyCaseMiddleware from 'axios-case-converter'
|
||||||
|
|
||||||
|
export let sni = applyCaseMiddleware(
|
||||||
|
axios.create({
|
||||||
|
baseURL: 'https://api.sn.solsynth.dev',
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
ignoreParams: true,
|
||||||
|
ignoreHeaders: true,
|
||||||
|
},
|
||||||
|
)
|
Loading…
Reference in New Issue
Block a user