✨ Open ID connect
This commit is contained in:
		| @@ -15,6 +15,9 @@ const fontRoboto = Roboto({ | ||||
|  | ||||
| const siteTheme = createTheme({ | ||||
|   cssVariables: true, | ||||
|   // colorSchemes: { | ||||
|   //   dark: true, | ||||
|   // }, | ||||
|   palette: { | ||||
|     mode: 'light', | ||||
|     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 'animate.css' | ||||
|  | ||||
| export default function AccountConfirm() { | ||||
| export default function AccountDeletion() { | ||||
|   const router = useRouter() | ||||
|  | ||||
|   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 'animate.css' | ||||
|  | ||||
| export type SnResetPasswordForm = { | ||||
|   password: string | ||||
| } | ||||
|  | ||||
| export default function AccountConfirm() { | ||||
| export default function AccountPasswordReset() { | ||||
|   const router = useRouter() | ||||
|  | ||||
|   const { handleSubmit, register } = useForm<SnResetPasswordForm>() | ||||
|   | ||||
		Reference in New Issue
	
	Block a user