From 6aced3ddf74ac978837ec3f4e92c47d0dafe8581 Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Wed, 1 Jan 2025 22:44:35 +0800 Subject: [PATCH] :sparkles: User login --- .prettierrc | 7 ++ README.md | 40 ----------- bun.lockb | Bin 179303 -> 187686 bytes package.json | 6 +- src/components/CapAppBar.tsx | 46 ++++++------ src/components/auth/SnLoginCheckpoint.tsx | 83 ++++++++++++++++++++++ src/components/auth/SnLoginRouter.tsx | 68 ++++++++++++++++++ src/components/auth/SnLoginStart.tsx | 67 +++++++++++++++++ src/pages/_app.tsx | 32 +++++---- src/pages/_document.tsx | 4 +- src/pages/auth/login.tsx | 79 ++++++++++++++++++++ src/pages/index.tsx | 4 +- src/services/auth.ts | 34 +++++++++ src/services/network.ts | 12 ++++ 14 files changed, 399 insertions(+), 83 deletions(-) create mode 100644 .prettierrc delete mode 100644 README.md create mode 100644 src/components/auth/SnLoginCheckpoint.tsx create mode 100644 src/components/auth/SnLoginRouter.tsx create mode 100644 src/components/auth/SnLoginStart.tsx create mode 100644 src/pages/auth/login.tsx create mode 100644 src/services/auth.ts create mode 100644 src/services/network.ts diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..4d6abf3 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "printWidth": 120, + "tabWidth": 2, + "trailingComma": "all", + "singleQuote": true +} diff --git a/README.md b/README.md deleted file mode 100644 index ef0e47e..0000000 --- a/README.md +++ /dev/null @@ -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. diff --git a/bun.lockb b/bun.lockb index 14d072cc46857c62378e8ab2c278613abb7a5014..08746b80a9df98148e5974f46a5139a25c25ced8 100755 GIT binary patch delta 40041 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 nYdRuhjLc8hgJkEcUCa4@KG-0o diff --git a/package.json b/package.json index 8f41033..d42d8f0 100644 --- a/package.json +++ b/package.json @@ -13,9 +13,13 @@ "@emotion/styled": "^11.14.0", "@mui/icons-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", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "react-hook-form": "^7.54.2" }, "devDependencies": { "typescript": "^5", diff --git a/src/components/CapAppBar.tsx b/src/components/CapAppBar.tsx index 7fc58f8..c5752cd 100644 --- a/src/components/CapAppBar.tsx +++ b/src/components/CapAppBar.tsx @@ -1,35 +1,35 @@ -import { AppBar, Box, IconButton, Toolbar, Typography } from "@mui/material"; -import MenuIcon from "@mui/icons-material/Menu"; -import AccountCircle from "@mui/icons-material/AccountCircle"; +import { AppBar, Box, IconButton, Toolbar, Typography } from '@mui/material' +import MenuIcon from '@mui/icons-material/Menu' +import AccountCircle from '@mui/icons-material/AccountCircle' +import Link from 'next/link' export function CapAppBar() { return ( - + - - Capital - - - - + + + Capital + + + + + + + + - ); + ) } diff --git a/src/components/auth/SnLoginCheckpoint.tsx b/src/components/auth/SnLoginCheckpoint.tsx new file mode 100644 index 0000000..e10d16d --- /dev/null +++ b/src/components/auth/SnLoginCheckpoint.tsx @@ -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(null) + const [loading, setLoading] = useState(false) + + const { handleSubmit, register } = useForm() + + async function onSubmit(data: any) { + try { + setLoading(true) + const resp = await sni.patch('/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 ( + <> + + } severity="error"> + {error} + + + +
+ + + + + +
+ + ) +} diff --git a/src/components/auth/SnLoginRouter.tsx b/src/components/auth/SnLoginRouter.tsx new file mode 100644 index 0000000..54187c1 --- /dev/null +++ b/src/components/auth/SnLoginRouter.tsx @@ -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 = [, ] + const factorTypeLabels = ['Password', 'Email verification code'] + + const [error, setError] = useState(null) + const [loading, setLoading] = useState(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 ( + <> + + } severity="error"> + {error} + + + + + + {factorList.map((factor) => ( + + ))} + + + + {ticket.stepRemain} step(s) left + + + + ) +} diff --git a/src/components/auth/SnLoginStart.tsx b/src/components/auth/SnLoginStart.tsx new file mode 100644 index 0000000..0373ae2 --- /dev/null +++ b/src/components/auth/SnLoginStart.tsx @@ -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(null) + const [loading, setLoading] = useState(false) + + const { handleSubmit, register } = useForm() + + async function onSubmit(data: any) { + try { + setLoading(true) + const resp = await sni.post('/cgi/id/auth', data) + const factorResp = await sni.get('/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 ( + <> + + } severity="error"> + {error} + + + +
+ + + + + + + By continuing means you agree to our Terms of Service and{' '} + Privacy Policy + + +
+ + ) +} diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index ff5c974..57e4d4e 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,26 +1,26 @@ -import "@/styles/globals.css"; -import type { AppProps } from "next/app"; -import { createTheme, CssBaseline, ThemeProvider } from "@mui/material"; -import { Roboto } from "next/font/google"; -import { CapAppBar } from "@/components/CapAppBar"; +import '@/styles/globals.css' +import type { AppProps } from 'next/app' +import { Box, createTheme, CssBaseline, ThemeProvider } from '@mui/material' +import { Roboto } from 'next/font/google' +import { CapAppBar } from '@/components/CapAppBar' const fontRoboto = Roboto({ - subsets: ["latin"], - weight: ["400", "500", "700"], - display: "swap", -}); + subsets: ['latin'], + weight: ['400', '500', '700'], + display: 'swap', +}) const siteTheme = createTheme({ palette: { - mode: "light", + mode: 'light', primary: { - main: "#3949ab", + main: '#3949ab', }, secondary: { - main: "#1e88e5", + main: '#1e88e5', }, }, -}); +}) export default function App({ Component, pageProps }: AppProps) { return ( @@ -35,8 +35,10 @@ export default function App({ Component, pageProps }: AppProps) { - + + + - ); + ) } diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 628a733..89f88e1 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -1,4 +1,4 @@ -import { Html, Head, Main, NextScript } from "next/document"; +import { Html, Head, Main, NextScript } from 'next/document' export default function Document() { return ( @@ -9,5 +9,5 @@ export default function Document() { - ); + ) } diff --git a/src/pages/auth/login.tsx b/src/pages/auth/login.tsx new file mode 100644 index 0000000..f9d1863 --- /dev/null +++ b/src/pages/auth/login.tsx @@ -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(0) + const [ticket, setTicket] = useState(null) + const [factorList, setFactorList] = useState([]) + const [factor, setFactor] = useState(null) + + const router = useRouter() + + function renderForm() { + switch (period) { + case 1: + return ( + { + setPeriod(period + 1) + setFactor(val) + }} + /> + ) + case 2: + return ( + { + if (!done) { + setTicket(val) + setPeriod(1) + return + } + router.push('/') + }} + /> + ) + default: + return ( + { + setPeriod(period + 1) + setTicket(val) + setFactorList(fcs) + }} + /> + ) + } + } + + return ( + + + + Login + + + Login via Solarpass + + + {renderForm()} + + + ) +} diff --git a/src/pages/index.tsx b/src/pages/index.tsx index fe0a7c3..85e85d1 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -1,4 +1,4 @@ -import { Container, Typography } from "@mui/material"; +import { Container, Typography } from '@mui/material' export default function Home() { return ( @@ -10,5 +10,5 @@ export default function Home() { - ); + ) } diff --git a/src/services/auth.ts b/src/services/auth.ts new file mode 100644 index 0000000..1d3e6ea --- /dev/null +++ b/src/services/auth.ts @@ -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 | null + accountId?: number | null +} diff --git a/src/services/network.ts b/src/services/network.ts new file mode 100644 index 0000000..755a0ab --- /dev/null +++ b/src/services/network.ts @@ -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, + }, +)