Open ID connect

This commit is contained in:
LittleSheep 2025-01-04 18:24:23 +08:00
parent d92ffbae5d
commit e670c571c7
7 changed files with 171 additions and 6 deletions

View File

@ -18,6 +18,14 @@ const nextConfig: NextConfig = {
},
],
},
async rewrites() {
return [
{
source: '/.well-known/:path*',
destination: '/api/well-known/:path*',
},
]
},
}
export default nextConfig

View File

@ -15,6 +15,9 @@ const fontRoboto = Roboto({
const siteTheme = createTheme({
cssVariables: true,
// colorSchemes: {
// dark: true,
// },
palette: {
mode: 'light',
primary: {

View File

@ -0,0 +1,10 @@
import axios from 'axios'
import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(_: NextApiRequest, res: NextApiResponse) {
const solarNetworkApi = 'https://api.sn.solsynth.dev'
const resp = await axios.get(solarNetworkApi + '/cgi/id/well-known/jwks')
res.status(200).json(resp.data)
}

View File

@ -0,0 +1,23 @@
import axios from 'axios'
import { NextApiRequest, NextApiResponse } from 'next'
export default async function handler(_: NextApiRequest, res: NextApiResponse) {
const siteUrl = 'https://solsynth.dev'
const solarNetworkApi = 'https://api.sn.solsynth.dev'
const resp = await axios.get(solarNetworkApi + '/cgi/id/well-known/openid-configuration')
const out: Record<string, any> = resp.data
out['authorization_endpoint'] = siteUrl + '/auth/authorize'
out['jwks_uri'] = siteUrl + '/.well-known/jwks'
for (let [k, v] of Object.entries(out)) {
if (typeof v === 'string') {
if (v.startsWith('https://id.solsynth.dev/api')) {
out[k] = v.replace('https://id.solsynth.dev/api', solarNetworkApi + '/cgi/id')
}
}
}
res.status(200).json(out)
}

View File

@ -0,0 +1,125 @@
import { sni } from '@/services/network'
import { Container, Box, Typography, Alert, Collapse, Button, CircularProgress, Card, CardContent } from '@mui/material'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import ErrorIcon from '@mui/icons-material/Error'
import CloseIcon from '@mui/icons-material/Close'
import CheckIcon from '@mui/icons-material/Check'
import { SnAuthTicket } from '@/services/auth'
export default function AccountAuthorize() {
const router = useRouter()
const [thirdClient, setThirdClient] = useState<any>(null)
const [error, setError] = useState<string | null>(null)
const [reverting, setReverting] = useState(false)
const [busy, setBusy] = useState(false)
function doCallback(ticket: SnAuthTicket) {
const url = `${router.query['redirect_uri']}?code=${ticket.grantToken}&state=${router.query['state']}`
window.open(url, '_self')
}
async function fetch() {
try {
setReverting(true)
const resp = await sni.get<{ ticket: SnAuthTicket; client: any }>(
'/cgi/id/auth/o/authorize' + window.location.search,
{},
)
if (resp.data.ticket) {
return doCallback(resp.data.ticket)
}
setThirdClient(resp.data.client)
} catch (err: any) {
setError(err.toString())
} finally {
setReverting(false)
}
}
useEffect(() => {
fetch()
}, [])
async function confirm() {
try {
setBusy(true)
const resp = await sni.post<{ ticket: SnAuthTicket }>('/cgi/id/auth/o/authorize' + window.location.search)
return doCallback(resp.data.ticket)
} catch (err: any) {
setError(err.toString())
} finally {
setBusy(false)
}
}
function decline() {
if (window.history.length > 0) {
window.history.back()
} else {
window.close()
}
}
return (
<Container
sx={{
display: 'grid',
placeItems: 'center',
height: 'calc(100vh - 64px)',
textAlign: 'center',
}}
maxWidth="xs"
>
<Box sx={{ width: '100%' }}>
<Typography variant="h5" component="h1">
Connect with Solarpass
</Typography>
<Typography variant="subtitle2" component="h2">
Connect third-party services with Solar Network
</Typography>
<Collapse in={!!error} sx={{ width: '100%' }}>
<Alert sx={{ mt: 4 }} icon={<ErrorIcon fontSize="inherit" />} severity="error">
{error}
</Alert>
</Collapse>
{reverting && (
<Box sx={{ mt: 3 }}>
<CircularProgress />
</Box>
)}
{!reverting && (
<Box sx={{ mt: 3 }}>
<Card variant="outlined" sx={{ width: '100%' }}>
<CardContent sx={{ textAlign: 'left', px: 2.5 }}>
<Typography variant="h6">{thirdClient?.name}</Typography>
<Typography variant="body2">{thirdClient?.description}</Typography>
</CardContent>
</Card>
<Box display="flex" justifyContent="space-between">
<Button sx={{ mt: 3 }} startIcon={<CloseIcon />} onClick={() => decline()} disabled={busy} color="error">
Decline
</Button>
<Button
sx={{ mt: 3 }}
startIcon={<CheckIcon />}
onClick={() => confirm()}
disabled={busy}
color="success"
>
Approve
</Button>
</Box>
</Box>
)}
</Box>
</Container>
)
}

View File

@ -5,9 +5,7 @@ import { useState } from 'react'
import ErrorIcon from '@mui/icons-material/Error'
import 'animate.css'
export default function AccountConfirm() {
export default function AccountDeletion() {
const router = useRouter()
const [error, setError] = useState<string | null>(null)

View File

@ -6,13 +6,11 @@ import { useForm } from 'react-hook-form'
import ErrorIcon from '@mui/icons-material/Error'
import 'animate.css'
export type SnResetPasswordForm = {
password: string
}
export default function AccountConfirm() {
export default function AccountPasswordReset() {
const router = useRouter()
const { handleSubmit, register } = useForm<SnResetPasswordForm>()