✨ Open ID connect
This commit is contained in:
parent
d92ffbae5d
commit
e670c571c7
@ -18,6 +18,14 @@ const nextConfig: NextConfig = {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
async rewrites() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/.well-known/:path*',
|
||||||
|
destination: '/api/well-known/:path*',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export default nextConfig
|
export default nextConfig
|
||||||
|
@ -15,6 +15,9 @@ const fontRoboto = Roboto({
|
|||||||
|
|
||||||
const siteTheme = createTheme({
|
const siteTheme = createTheme({
|
||||||
cssVariables: true,
|
cssVariables: true,
|
||||||
|
// colorSchemes: {
|
||||||
|
// dark: true,
|
||||||
|
// },
|
||||||
palette: {
|
palette: {
|
||||||
mode: 'light',
|
mode: 'light',
|
||||||
primary: {
|
primary: {
|
||||||
|
10
src/pages/api/well-known/jwks.ts
Normal file
10
src/pages/api/well-known/jwks.ts
Normal 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)
|
||||||
|
}
|
23
src/pages/api/well-known/openid-configuration.ts
Normal file
23
src/pages/api/well-known/openid-configuration.ts
Normal 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)
|
||||||
|
}
|
125
src/pages/auth/authorize.tsx
Normal file
125
src/pages/auth/authorize.tsx
Normal 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>
|
||||||
|
)
|
||||||
|
}
|
@ -5,9 +5,7 @@ import { useState } from 'react'
|
|||||||
|
|
||||||
import ErrorIcon from '@mui/icons-material/Error'
|
import ErrorIcon from '@mui/icons-material/Error'
|
||||||
|
|
||||||
import 'animate.css'
|
export default function AccountDeletion() {
|
||||||
|
|
||||||
export default function AccountConfirm() {
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
@ -6,13 +6,11 @@ import { useForm } from 'react-hook-form'
|
|||||||
|
|
||||||
import ErrorIcon from '@mui/icons-material/Error'
|
import ErrorIcon from '@mui/icons-material/Error'
|
||||||
|
|
||||||
import 'animate.css'
|
|
||||||
|
|
||||||
export type SnResetPasswordForm = {
|
export type SnResetPasswordForm = {
|
||||||
password: string
|
password: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AccountConfirm() {
|
export default function AccountPasswordReset() {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const { handleSubmit, register } = useForm<SnResetPasswordForm>()
|
const { handleSubmit, register } = useForm<SnResetPasswordForm>()
|
||||||
|
Loading…
Reference in New Issue
Block a user