[release] v0.6.0-unstable2
This commit is contained in:
parent
ebe8423a2d
commit
1970f14eac
13
client/src/components/delete.jsx
Normal file
13
client/src/components/delete.jsx
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
import { CheckOutlined, ClockCircleOutlined, DashboardOutlined, DeleteOutlined, DownOutlined, LockOutlined, SafetyOutlined, UpOutlined } from "@ant-design/icons";
|
||||||
|
import { Card, Chip, Stack, Tooltip } from "@mui/material";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
|
||||||
|
export const DeleteButton = ({onDelete}) => {
|
||||||
|
const [confirmDelete, setConfirmDelete] = useState(false);
|
||||||
|
|
||||||
|
return (<>
|
||||||
|
{!confirmDelete && (<Chip label={<DeleteOutlined />} onClick={() => setConfirmDelete(true)}/>)}
|
||||||
|
{confirmDelete && (<Chip label={<CheckOutlined />} color="error" onClick={(event) => onDelete(event)}/>)}
|
||||||
|
</>);
|
||||||
|
}
|
|
@ -45,7 +45,7 @@ export const RouteMode = ({route}) => {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const isDark = theme.palette.mode === 'dark';
|
const isDark = theme.palette.mode === 'dark';
|
||||||
let c = routeImages[route.Mode.toUpperCase()];
|
let c = routeImages[route.Mode.toUpperCase()];
|
||||||
return <>
|
return c ? <>
|
||||||
<Chip
|
<Chip
|
||||||
icon={<span>{c.icon}</span>}
|
icon={<span>{c.icon}</span>}
|
||||||
label={c.label}
|
label={c.label}
|
||||||
|
@ -56,7 +56,7 @@ export const RouteMode = ({route}) => {
|
||||||
alignItems: "right",
|
alignItems: "right",
|
||||||
}}
|
}}
|
||||||
></Chip>
|
></Chip>
|
||||||
</>
|
</> : <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const RouteSecurity = ({route}) => {
|
export const RouteSecurity = ({route}) => {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
// assets
|
// assets
|
||||||
import { ProfileOutlined, SettingOutlined, NodeExpandOutlined, AppstoreOutlined} from '@ant-design/icons';
|
import { ProfileOutlined, PicLeftOutlined, SettingOutlined, NodeExpandOutlined, AppstoreOutlined} from '@ant-design/icons';
|
||||||
|
|
||||||
// icons
|
// icons
|
||||||
const icons = {
|
const icons = {
|
||||||
|
@ -36,6 +36,13 @@ const pages = {
|
||||||
url: '/ui/config-users',
|
url: '/ui/config-users',
|
||||||
icon: icons.ProfileOutlined,
|
icon: icons.ProfileOutlined,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'openid',
|
||||||
|
title: 'OpenID',
|
||||||
|
type: 'item',
|
||||||
|
url: '/ui/openid-manage',
|
||||||
|
icon: PicLeftOutlined,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'config',
|
id: 'config',
|
||||||
title: 'Configuration',
|
title: 'Configuration',
|
||||||
|
|
|
@ -7,10 +7,15 @@ import { Grid, Stack, Typography } from '@mui/material';
|
||||||
import AuthLogin from './auth-forms/AuthLogin';
|
import AuthLogin from './auth-forms/AuthLogin';
|
||||||
import AuthWrapper from './AuthWrapper';
|
import AuthWrapper from './AuthWrapper';
|
||||||
|
|
||||||
|
const selfport = new URL(window.location.href).port
|
||||||
|
const selfprotocol = new URL(window.location.href).protocol + "//"
|
||||||
|
const selfHostname = selfprotocol + (new URL(window.location.href).hostname) + (selfport ? ":" + selfport : "")
|
||||||
|
|
||||||
// ================================|| LOGIN ||================================ //
|
// ================================|| LOGIN ||================================ //
|
||||||
|
|
||||||
const Login = () => (
|
const Login = () => (
|
||||||
<AuthWrapper>
|
<AuthWrapper>
|
||||||
|
<link rel="openid2.provider openid.server" href={selfHostname + "/oauth2/auth"} />
|
||||||
<Grid container spacing={3}>
|
<Grid container spacing={3}>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
<Stack direction="row" justifyContent="space-between" alignItems="baseline" sx={{ mb: { xs: -0.5, sm: 0.5 } }}>
|
||||||
|
|
|
@ -22,14 +22,22 @@ const OpenID = () => {
|
||||||
const entireSearch = searchParams.toString()
|
const entireSearch = searchParams.toString()
|
||||||
const [checkedScopes, setCheckedScopes] = useState(["openid"])
|
const [checkedScopes, setCheckedScopes] = useState(["openid"])
|
||||||
|
|
||||||
|
let icon;
|
||||||
|
|
||||||
// get hostname from redirect_uri with port
|
// get hostname from redirect_uri with port
|
||||||
const port = new URL(redirect_uri).port
|
let port, protocol, appHostname;
|
||||||
const protocol = new URL(redirect_uri).protocol + "//"
|
|
||||||
const appHostname = protocol + (new URL(redirect_uri).hostname) + (port ? ":" + port : "")
|
try {
|
||||||
const icon = getFaviconURL({
|
port = new URL(redirect_uri).port
|
||||||
Mode: 'PROXY',
|
protocol = new URL(redirect_uri).protocol + "//"
|
||||||
Target: appHostname
|
appHostname = protocol + (new URL(redirect_uri).hostname) + (port ? ":" + port : "")
|
||||||
});
|
icon = getFaviconURL({
|
||||||
|
Mode: 'PROXY',
|
||||||
|
Target: appHostname
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
icon = getFaviconURL();
|
||||||
|
}
|
||||||
|
|
||||||
const selfport = new URL(window.location.href).port
|
const selfport = new URL(window.location.href).port
|
||||||
const selfprotocol = new URL(window.location.href).protocol + "//"
|
const selfprotocol = new URL(window.location.href).protocol + "//"
|
||||||
|
@ -55,7 +63,7 @@ const OpenID = () => {
|
||||||
}}>
|
}}>
|
||||||
<img src={icon} alt={'icon'} width="64px" />
|
<img src={icon} alt={'icon'} width="64px" />
|
||||||
<div>
|
<div>
|
||||||
You are logging in to <b>{client_id}</b>. <br />
|
You are about to login into <b>{client_id}</b>. <br />
|
||||||
Check which permissions you are giving to this application. <br />
|
Check which permissions you are giving to this application. <br />
|
||||||
</div>
|
</div>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -84,7 +92,7 @@ const OpenID = () => {
|
||||||
opacity: '0.8',
|
opacity: '0.8',
|
||||||
fontStyle: 'italic',
|
fontStyle: 'italic',
|
||||||
}}>
|
}}>
|
||||||
You will be redirected to <b>{appHostname}</b> after login. <br />
|
You will be redirected to <b>{redirect_uri}</b> after login. <br />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<LoadingButton
|
<LoadingButton
|
||||||
|
|
99
client/src/pages/openid/openid-edit.jsx
Normal file
99
client/src/pages/openid/openid-edit.jsx
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
// material-ui
|
||||||
|
import { AppstoreAddOutlined, PlusCircleOutlined, ReloadOutlined, SearchOutlined, SettingOutlined } from '@ant-design/icons';
|
||||||
|
import { Alert, Badge, Button, Card, Checkbox, Chip, CircularProgress, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Divider, Input, InputAdornment, TextField, Tooltip, Typography } from '@mui/material';
|
||||||
|
import Grid2 from '@mui/material/Unstable_Grid2/Grid2';
|
||||||
|
import { Stack } from '@mui/system';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import Paper from '@mui/material/Paper';
|
||||||
|
import { styled } from '@mui/material/styles';
|
||||||
|
|
||||||
|
import * as API from '../../api';
|
||||||
|
import IsLoggedIn from '../../isLoggedIn';
|
||||||
|
import RestartModal from '../config/users/restart';
|
||||||
|
import RouteManagement from '../config/routes/routeman';
|
||||||
|
import { ValidateRoute, getFaviconURL, sanitizeRoute } from '../../utils/routes';
|
||||||
|
import HostChip from '../../components/hostChip';
|
||||||
|
import { Formik, useFormik } from 'formik';
|
||||||
|
import * as yup from 'yup';
|
||||||
|
|
||||||
|
const OpenIdEditModal = ({ clientId, openNewModal, setOpenNewModal, config, onSubmit }) => {
|
||||||
|
const [openRestartModal, setOpenRestartModal] = useState(false);
|
||||||
|
const [submitErrors, setSubmitErrors] = useState([]);
|
||||||
|
const [newRoute, setNewRoute] = useState(null);
|
||||||
|
|
||||||
|
function addRoute() {
|
||||||
|
return API.config.addRoute(newRoute).then((res) => {
|
||||||
|
setOpenNewModal(false);
|
||||||
|
setOpenRestartModal(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientConfig = Object.values(config.OpenIDClients).find((c) => c.id === clientId);
|
||||||
|
|
||||||
|
return <>
|
||||||
|
<RestartModal openModal={openRestartModal} setOpenModal={setOpenRestartModal} />
|
||||||
|
<Dialog open={openNewModal} onClose={() => setOpenNewModal(false)}>
|
||||||
|
<Formik
|
||||||
|
initialValues={{
|
||||||
|
id: clientConfig ? clientConfig.id : '',
|
||||||
|
redirect: clientConfig ? clientConfig.redirect : '',
|
||||||
|
}}
|
||||||
|
validationSchema={yup.object({
|
||||||
|
id: yup.string().required('Required'),
|
||||||
|
redirect: yup.string().required('Required'),
|
||||||
|
})}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
onSubmit && onSubmit(values);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(formik) => (
|
||||||
|
<form onSubmit={formik.handleSubmit}>
|
||||||
|
<DialogTitle>{clientId ? clientId : "New client"}</DialogTitle>
|
||||||
|
{openNewModal && <>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
<Stack spacing={2} style={{ marginTop: '10px', width: '500px', maxWidth: '100%' }}>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
id="id"
|
||||||
|
name="id"
|
||||||
|
label="ID"
|
||||||
|
value={formik.values.id}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error={formik.touched.id && Boolean(formik.errors.id)}
|
||||||
|
helperText={formik.touched.id && formik.errors.id}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
fullWidth
|
||||||
|
id="redirect"
|
||||||
|
name="redirect"
|
||||||
|
label="Redirect"
|
||||||
|
value={formik.values.redirect}
|
||||||
|
onChange={formik.handleChange}
|
||||||
|
error={formik.touched.redirect && Boolean(formik.errors.redirect)}
|
||||||
|
helperText={formik.touched.redirect && formik.errors.redirect}
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
{submitErrors && submitErrors.length > 0 && <Stack spacing={2} direction={"column"}>
|
||||||
|
<Alert severity="error">{submitErrors.map((err) => {
|
||||||
|
return <div>{err}</div>
|
||||||
|
})}</Alert>
|
||||||
|
</Stack>}
|
||||||
|
<Button onClick={() => setOpenNewModal(false)}>Cancel</Button>
|
||||||
|
<Button color="primary" variant="contained" type="submit" onClick={() => {
|
||||||
|
formik.handleSubmit();
|
||||||
|
}}>{clientId ? "Edit" : "Create"}</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</>}
|
||||||
|
</form>
|
||||||
|
|
||||||
|
)}
|
||||||
|
</Formik>
|
||||||
|
</Dialog>
|
||||||
|
</>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OpenIdEditModal;
|
236
client/src/pages/openid/openid-list.jsx
Normal file
236
client/src/pages/openid/openid-list.jsx
Normal file
|
@ -0,0 +1,236 @@
|
||||||
|
import * as React from 'react';
|
||||||
|
import IsLoggedIn from '../../isLoggedIn';
|
||||||
|
import * as API from '../../api';
|
||||||
|
import MainCard from '../../components/MainCard';
|
||||||
|
import { Formik, Field } from 'formik';
|
||||||
|
import * as Yup from 'yup';
|
||||||
|
import { useTheme } from '@mui/material/styles';
|
||||||
|
import { WarningOutlined, PlusCircleOutlined, CopyOutlined, ExclamationCircleOutlined, SyncOutlined, UserOutlined, KeyOutlined, ArrowRightOutlined } from '@ant-design/icons';
|
||||||
|
import {
|
||||||
|
Alert,
|
||||||
|
Button,
|
||||||
|
Checkbox,
|
||||||
|
Stack,
|
||||||
|
Typography,
|
||||||
|
FormHelperText,
|
||||||
|
Chip,
|
||||||
|
CircularProgress,
|
||||||
|
Dialog,
|
||||||
|
DialogTitle,
|
||||||
|
DialogContent,
|
||||||
|
DialogContentText,
|
||||||
|
DialogActions,
|
||||||
|
IconButton,
|
||||||
|
|
||||||
|
} from '@mui/material';
|
||||||
|
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||||
|
import AnimateButton from '../../components/@extended/AnimateButton';
|
||||||
|
import RestartModal from '../config/users/restart';
|
||||||
|
import RouteManagement from '../config/routes/routeman';
|
||||||
|
import { map } from 'lodash';
|
||||||
|
import { getFaviconURL, sanitizeRoute, ValidateRoute } from '../../utils/routes';
|
||||||
|
import PrettyTableView from '../../components/tableView/prettyTableView';
|
||||||
|
import HostChip from '../../components/hostChip';
|
||||||
|
import { RouteActions, RouteMode, RouteSecurity } from '../../components/routeComponents';
|
||||||
|
import { useNavigate } from 'react-router';
|
||||||
|
import NewRouteCreate from '../config/routes/newRoute';
|
||||||
|
import { DeleteButton } from '../../components/delete';
|
||||||
|
import OpenIdEditModal from './openid-edit';
|
||||||
|
|
||||||
|
import bcrypt from 'bcryptjs';
|
||||||
|
|
||||||
|
const stickyButton = {
|
||||||
|
position: 'fixed',
|
||||||
|
bottom: '20px',
|
||||||
|
width: '300px',
|
||||||
|
boxShadow: '0px 0px 10px 0px rgba(0,0,0,0.50)',
|
||||||
|
right: '20px',
|
||||||
|
}
|
||||||
|
|
||||||
|
function shorten(test) {
|
||||||
|
if (test.length > 75) {
|
||||||
|
return test.substring(0, 75) + '...';
|
||||||
|
}
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
const OpenIdList = () => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const isDark = theme.palette.mode === 'dark';
|
||||||
|
const [config, setConfig] = React.useState(null);
|
||||||
|
const [openModal, setOpenModal] = React.useState(false);
|
||||||
|
const [error, setError] = React.useState(null);
|
||||||
|
const [submitErrors, setSubmitErrors] = React.useState([]);
|
||||||
|
const [needSave, setNeedSave] = React.useState(false);
|
||||||
|
const [clientId, setClientId] = React.useState(false);
|
||||||
|
const [openNewModal, setOpenNewModal] = React.useState(false);
|
||||||
|
const [newSecret, setNewSecret] = React.useState(null);
|
||||||
|
|
||||||
|
function updateRoutes(routes) {
|
||||||
|
let con = {
|
||||||
|
...config,
|
||||||
|
OpenIDClients: routes,
|
||||||
|
};
|
||||||
|
setConfig(con);
|
||||||
|
return con;
|
||||||
|
}
|
||||||
|
|
||||||
|
function refresh() {
|
||||||
|
API.config.get().then((res) => {
|
||||||
|
setConfig(res.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function save(con) {
|
||||||
|
API.config.set(con).then((res) => {
|
||||||
|
refresh();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getClientIcon(client) {
|
||||||
|
try {
|
||||||
|
let hostname = new URL(client.redirect).hostname;
|
||||||
|
let port = new URL(client.redirect).port;
|
||||||
|
let protocol = new URL(client.redirect).protocol + '//';
|
||||||
|
return getFaviconURL({
|
||||||
|
Mode: 'PROXY',
|
||||||
|
Target: protocol + hostname + (port ? ':' + port : ''),
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return getFaviconURL()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteClient(event, key) {
|
||||||
|
event.stopPropagation();
|
||||||
|
clients.splice(key, 1);
|
||||||
|
save(updateRoutes(clients));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
React.useEffect(() => {
|
||||||
|
refresh();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const generateNewSecret = (clientIdToUpdate) => {
|
||||||
|
let newSecret = Math.random().toString(36).substring(2, 24) + Math.random().toString(36).substring(2, 15);
|
||||||
|
let encryptedSecret = bcrypt.hashSync(newSecret, 10);
|
||||||
|
console.log(newSecret, encryptedSecret)
|
||||||
|
let index = clients.findIndex((r) => r.id === clientIdToUpdate);
|
||||||
|
clients[index].secret = encryptedSecret;
|
||||||
|
save(updateRoutes(clients));
|
||||||
|
setNewSecret(newSecret);
|
||||||
|
}
|
||||||
|
|
||||||
|
let clients = config && (config.OpenIDClients || []);
|
||||||
|
|
||||||
|
return <div style={{}}>
|
||||||
|
<IsLoggedIn />
|
||||||
|
<Stack direction="row" spacing={1} style={{ marginBottom: '20px' }}>
|
||||||
|
<Button variant="contained" color="primary" startIcon={<SyncOutlined />} onClick={() => {
|
||||||
|
refresh();
|
||||||
|
}}>Refresh</Button>
|
||||||
|
<Button variant="contained" color="primary" startIcon={<PlusCircleOutlined />} onClick={() => {
|
||||||
|
setClientId(null);
|
||||||
|
setOpenNewModal(true);
|
||||||
|
}}>Create</Button>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
{config && <>
|
||||||
|
<OpenIdEditModal
|
||||||
|
clientId={clientId}
|
||||||
|
openNewModal={openNewModal}
|
||||||
|
setOpenNewModal={setOpenNewModal}
|
||||||
|
config={config}
|
||||||
|
onSubmit={(values) => {
|
||||||
|
if (clientId) {
|
||||||
|
let index = clients.findIndex((r) => r.id === clientId);
|
||||||
|
clients[index] = values;
|
||||||
|
} else {
|
||||||
|
clients.push(values);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(updateRoutes(clients));
|
||||||
|
setOpenNewModal(false);
|
||||||
|
setClientId(null);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{newSecret && <Dialog open={newSecret} onClose={() => setNewSecret(false)}>
|
||||||
|
<DialogTitle>New Secret</DialogTitle>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogContentText>
|
||||||
|
Secret has been updated. Please copy it now as it will not be shown again.
|
||||||
|
|
||||||
|
<Stack direction="row" spacing={2} style={{ marginTop: '10px', width: '100%', maxWidth: '100%' }}>
|
||||||
|
<div style={{overflowX: 'scroll', float: 'left', width: '100%', padding: '5px', background:'rgba(0,0,0,0.15)', whiteSpace: 'nowrap', wordBreak: 'keep-all', overflow: 'auto', fontStyle: 'italic'}}>
|
||||||
|
{newSecret}
|
||||||
|
</div>
|
||||||
|
<IconButton size="large" style={{float: 'left'}} aria-label="copy" onClick={
|
||||||
|
() => {
|
||||||
|
navigator.clipboard.writeText(newSecret);
|
||||||
|
}
|
||||||
|
}>
|
||||||
|
<CopyOutlined />
|
||||||
|
</IconButton>
|
||||||
|
</Stack>
|
||||||
|
</DialogContentText>
|
||||||
|
</DialogContent>
|
||||||
|
<DialogActions>
|
||||||
|
<Button onClick={() => setNewSecret(false)}>Close</Button>
|
||||||
|
</DialogActions>
|
||||||
|
</Dialog>}
|
||||||
|
|
||||||
|
{clients && <PrettyTableView
|
||||||
|
data={clients}
|
||||||
|
getKey={(r) => r.id}
|
||||||
|
onRowClick={(r) => {
|
||||||
|
setClientId(r.id);
|
||||||
|
setOpenNewModal(true);
|
||||||
|
}}
|
||||||
|
columns={[
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
field: (r) => <img className="loading-image" alt="" src={getClientIcon(r)} width="64px" />,
|
||||||
|
style: {
|
||||||
|
textAlign: 'center',
|
||||||
|
width: '100px',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Client ID',
|
||||||
|
search: (r) => r.id,
|
||||||
|
style: {
|
||||||
|
textDecoration: 'inherit',
|
||||||
|
},
|
||||||
|
underline: true,
|
||||||
|
field: (r) => <>
|
||||||
|
<div style={{ display: 'inline-block', textDecoration: 'inherit', fontSize: '125%', color: isDark ? theme.palette.primary.light : theme.palette.primary.dark }}>{r.id}</div><br />
|
||||||
|
</>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Redirect URI',
|
||||||
|
screenMin: 'sm',
|
||||||
|
search: (r) => r.redirect,
|
||||||
|
field: (r) => r.redirect,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '', clickable: true, field: (r, k) => <>
|
||||||
|
<Button variant="contained" color="primary" startIcon={<ArrowRightOutlined />} onClick={() => {
|
||||||
|
generateNewSecret(r.id)
|
||||||
|
}}>Reset Secret</Button>
|
||||||
|
<DeleteButton onDelete={(event) => deleteClient(event, k)} />
|
||||||
|
</>,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>}
|
||||||
|
{
|
||||||
|
!clients && <div style={{ textAlign: 'center' }}>
|
||||||
|
<CircularProgress />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</>}
|
||||||
|
</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OpenIdList;
|
|
@ -14,6 +14,7 @@ import HomePage from '../pages/home';
|
||||||
import ContainerIndex from '../pages/servapps/containers';
|
import ContainerIndex from '../pages/servapps/containers';
|
||||||
import NewDockerService from '../pages/servapps/containers/newService';
|
import NewDockerService from '../pages/servapps/containers/newService';
|
||||||
import NewDockerServiceForm from '../pages/servapps/containers/newServiceForm';
|
import NewDockerServiceForm from '../pages/servapps/containers/newServiceForm';
|
||||||
|
import OpenIdList from '../pages/openid/openid-list';
|
||||||
|
|
||||||
|
|
||||||
// render - dashboard
|
// render - dashboard
|
||||||
|
@ -81,6 +82,10 @@ const MainRoutes = {
|
||||||
path: '/ui/servapps/containers/:containerName',
|
path: '/ui/servapps/containers/:containerName',
|
||||||
element: <ContainerIndex />,
|
element: <ContainerIndex />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/ui/openid-manage',
|
||||||
|
element: <OpenIdList />,
|
||||||
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
9238
package-lock.json
generated
9238
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "cosmos-server",
|
"name": "cosmos-server",
|
||||||
"version": "0.6.0-unstable",
|
"version": "0.6.0-unstable2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "test-server.js",
|
"main": "test-server.js",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
|
@ -22,6 +22,8 @@
|
||||||
"@testing-library/user-event": "^14.4.3",
|
"@testing-library/user-event": "^14.4.3",
|
||||||
"@vitejs/plugin-react": "^3.1.0",
|
"@vitejs/plugin-react": "^3.1.0",
|
||||||
"apexcharts": "^3.35.5",
|
"apexcharts": "^3.35.5",
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"browserslist": "^4.21.7",
|
||||||
"formik": "^2.2.9",
|
"formik": "^2.2.9",
|
||||||
"framer-motion": "^7.3.6",
|
"framer-motion": "^7.3.6",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
|
@ -55,7 +57,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"client": "vite",
|
"client": "vite",
|
||||||
"client-build": "vite build --base=/ui/",
|
"client-build": "vite build --base=/ui/",
|
||||||
"start": "env COSMOS_HOSTNAME=localhost CONFIG_FILE=./config_dev.json EZ=UTC build/cosmos",
|
"start": "env CONFIG_FILE=./config_dev.json EZ=UTC build/cosmos",
|
||||||
"build": "sh build.sh",
|
"build": "sh build.sh",
|
||||||
"dev": "npm run build && npm run start",
|
"dev": "npm run build && npm run start",
|
||||||
"dockerdevbuild": "sh build.sh && docker build -f dockerfile.local --tag cosmos-dev .",
|
"dockerdevbuild": "sh build.sh && docker build -f dockerfile.local --tag cosmos-dev .",
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
package authorizationserver
|
package authorizationserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
"math/rand"
|
||||||
"crypto/rsa"
|
"crypto/rsa"
|
||||||
"github.com/ory/fosite"
|
"github.com/ory/fosite"
|
||||||
"time"
|
"time"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
|
||||||
"github.com/ory/fosite/compose"
|
"github.com/ory/fosite/compose"
|
||||||
"github.com/ory/fosite/handler/openid"
|
"github.com/ory/fosite/handler/openid"
|
||||||
|
@ -18,6 +21,53 @@ import (
|
||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var oauth2 fosite.OAuth2Provider
|
||||||
|
var AuthPrivateKey *rsa.PrivateKey
|
||||||
|
|
||||||
|
func Init() {
|
||||||
|
config := utils.ReadConfigFromFile()
|
||||||
|
authKey := config.HTTPConfig.AuthPrivateKey
|
||||||
|
|
||||||
|
secret := []byte(authKey[32:64])
|
||||||
|
|
||||||
|
foconfig := &fosite.Config{
|
||||||
|
AccessTokenLifespan: time.Minute * 30,
|
||||||
|
GlobalSecret: secret,
|
||||||
|
}
|
||||||
|
|
||||||
|
store := storage.NewMemoryStore()
|
||||||
|
|
||||||
|
// loop config.OpenIDClients
|
||||||
|
for _, client := range config.OpenIDClients {
|
||||||
|
utils.Log("Registering OpenID client: " + client.ID)
|
||||||
|
|
||||||
|
// register client
|
||||||
|
store.Clients[client.ID] = &fosite.DefaultClient{
|
||||||
|
ID: client.ID,
|
||||||
|
Secret: []byte(client.Secret),
|
||||||
|
RedirectURIs: []string{client.Redirect},
|
||||||
|
Scopes: []string{"openid", "email"},
|
||||||
|
ResponseTypes: []string{"id_token", "code", "token", "id_token token", "code id_token", "code token", "code id_token token"},
|
||||||
|
GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute a hash of your authKey to use as a seed for deterministic RSA key generation
|
||||||
|
seedHash := sha256.Sum256([]byte(authKey))
|
||||||
|
seed := int64(binary.BigEndian.Uint64(seedHash[:]))
|
||||||
|
|
||||||
|
// Now you can create a deterministic source of randomness
|
||||||
|
deterministicReader := rand.New(rand.NewSource(seed))
|
||||||
|
|
||||||
|
// privateKey is used to sign JWT tokens. The default strategy uses RS256 (RSA Signature with SHA-256)
|
||||||
|
AuthPrivateKey, _ = rsa.GenerateKey(deterministicReader, 2048)
|
||||||
|
|
||||||
|
// Build a fosite instance with all OAuth2 and OpenID Connect handlers enabled, plugging in our configurations as specified above.
|
||||||
|
oauth2 = compose.ComposeAllEnabled(foconfig, store, AuthPrivateKey)
|
||||||
|
|
||||||
|
utils.Log("OpenID server initialized")
|
||||||
|
}
|
||||||
|
|
||||||
func RegisterHandlers(wellKnown *mux.Router, userRouter *mux.Router, serverRouter *mux.Router) {
|
func RegisterHandlers(wellKnown *mux.Router, userRouter *mux.Router, serverRouter *mux.Router) {
|
||||||
// Set up oauth2 endpoints. You could also use gorilla/mux or any other router.
|
// Set up oauth2 endpoints. You could also use gorilla/mux or any other router.
|
||||||
userRouter.HandleFunc("/auth", authEndpoint)
|
userRouter.HandleFunc("/auth", authEndpoint)
|
||||||
|
@ -33,94 +83,8 @@ func RegisterHandlers(wellKnown *mux.Router, userRouter *mux.Router, serverRoute
|
||||||
// public endpoints
|
// public endpoints
|
||||||
wellKnown.HandleFunc("/openid-configuration", discoverEndpoint)
|
wellKnown.HandleFunc("/openid-configuration", discoverEndpoint)
|
||||||
wellKnown.HandleFunc("/jwks.json", jwksEndpoint)
|
wellKnown.HandleFunc("/jwks.json", jwksEndpoint)
|
||||||
|
|
||||||
|
|
||||||
store.Clients["gitea"] = &fosite.DefaultClient{
|
|
||||||
ID: "gitea",
|
|
||||||
Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar"
|
|
||||||
RedirectURIs: []string{"http://localhost:3000/user/oauth2/Cosmos/callback"},
|
|
||||||
Scopes: []string{"openid", "email", "profile"},
|
|
||||||
ResponseTypes: []string{"id_token", "code", "token", "id_token token", "code id_token", "code token", "code id_token token"},
|
|
||||||
GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"},
|
|
||||||
}
|
|
||||||
|
|
||||||
store.Clients["my-client"] = &fosite.DefaultClient{
|
|
||||||
ID: "my-client",
|
|
||||||
Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar"
|
|
||||||
RotatedSecrets: [][]byte{[]byte(`$2y$10$X51gLxUQJ.hGw1epgHTE5u0bt64xM0COU7K9iAp.OFg8p2pUd.1zC`)}, // = "foobaz",
|
|
||||||
RedirectURIs: []string{"http://localhost:3846/callback"},
|
|
||||||
ResponseTypes: []string{"id_token", "code", "token", "id_token token", "code id_token", "code token", "code id_token token"},
|
|
||||||
GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"},
|
|
||||||
Scopes: []string{"fosite", "openid", "photos", "offline"},
|
|
||||||
}
|
|
||||||
|
|
||||||
store.Clients["minio"] = &fosite.DefaultClient{
|
|
||||||
ID: "minio",
|
|
||||||
Secret: []byte(`$2a$10$IxMdI6d.LIRZPpSfEwNoeu4rY3FhDREsxFJXikcgdRRAStxUlsuEO`), // = "foobar"
|
|
||||||
RedirectURIs: []string{"http://localhost:9090/oauth_callback"},
|
|
||||||
Scopes: []string{"openid", "email", "profile", "groups"},
|
|
||||||
ResponseTypes: []string{"id_token", "code", "token", "id_token token", "code id_token", "code token", "code id_token token"},
|
|
||||||
GrantTypes: []string{"implicit", "refresh_token", "authorization_code", "password", "client_credentials"},
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// fosite requires four parameters for the server to get up and running:
|
|
||||||
// 1. config - for any enforcement you may desire, you can do this using `compose.Config`. You like PKCE, enforce it!
|
|
||||||
// 2. store - no auth service is generally useful unless it can remember clients and users.
|
|
||||||
// fosite is incredibly composable, and the store parameter enables you to build and BYODb (Bring Your Own Database)
|
|
||||||
// 3. secret - required for code, access and refresh token generation.
|
|
||||||
// 4. privateKey - required for id/jwt token generation.
|
|
||||||
var (
|
|
||||||
// Check the api documentation of `compose.Config` for further configuration options.
|
|
||||||
config = &fosite.Config{
|
|
||||||
AccessTokenLifespan: time.Minute * 30,
|
|
||||||
GlobalSecret: secret,
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is the example storage that contains:
|
|
||||||
// * an OAuth2 Client with id "my-client" and secrets "foobar" and "foobaz" capable of all oauth2 and open id connect grant and response types.
|
|
||||||
// * a User for the resource owner password credentials grant type with username "peter" and password "secret".
|
|
||||||
//
|
|
||||||
// You will most likely replace this with your own logic once you set up a real world application.
|
|
||||||
store = storage.NewMemoryStore()
|
|
||||||
|
|
||||||
// This secret is used to sign authorize codes, access and refresh tokens.
|
|
||||||
// It has to be 32-bytes long for HMAC signing. This requirement can be configured via `compose.Config` above.
|
|
||||||
// In order to generate secure keys, the best thing to do is use crypto/rand:
|
|
||||||
//
|
|
||||||
// ```
|
|
||||||
// package main
|
|
||||||
//
|
|
||||||
// import (
|
|
||||||
// "crypto/rand"
|
|
||||||
// "encoding/hex"
|
|
||||||
// "fmt"
|
|
||||||
// )
|
|
||||||
//
|
|
||||||
// func main() {
|
|
||||||
// var secret = make([]byte, 32)
|
|
||||||
// _, err := rand.Read(secret)
|
|
||||||
// if err != nil {
|
|
||||||
// panic(err)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// ```
|
|
||||||
//
|
|
||||||
// If you require this to key to be stable, for example, when running multiple fosite servers, you can generate the
|
|
||||||
// 32byte random key as above and push it out to a base64 encoded string.
|
|
||||||
// This can then be injected and decoded as the `var secret []byte` on server start.
|
|
||||||
secret = []byte("some-cool-secret-that-is-32bytes")
|
|
||||||
|
|
||||||
// privateKey is used to sign JWT tokens. The default strategy uses RS256 (RSA Signature with SHA-256)
|
|
||||||
AuthPrivateKey, _ = rsa.GenerateKey(rand.Reader, 2048)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
// Build a fosite instance with all OAuth2 and OpenID Connect handlers enabled, plugging in our configurations as specified above.
|
|
||||||
var oauth2 = compose.ComposeAllEnabled(config, store, AuthPrivateKey)
|
|
||||||
|
|
||||||
// A session is passed from the `/auth` to the `/token` endpoint. You probably want to store data like: "Who made the request",
|
// A session is passed from the `/auth` to the `/token` endpoint. You probably want to store data like: "Who made the request",
|
||||||
// "What organization does that person belong to" and so on.
|
// "What organization does that person belong to" and so on.
|
||||||
// For our use case, the session will meet the requirements imposed by JWT access tokens, HMAC access tokens and OpenID Connect
|
// For our use case, the session will meet the requirements imposed by JWT access tokens, HMAC access tokens and OpenID Connect
|
||||||
|
@ -132,6 +96,10 @@ var oauth2 = compose.ComposeAllEnabled(config, store, AuthPrivateKey)
|
||||||
//
|
//
|
||||||
// session = new(fosite.DefaultSession)
|
// session = new(fosite.DefaultSession)
|
||||||
func newSession(user string, req *http.Request) *openid.DefaultSession {
|
func newSession(user string, req *http.Request) *openid.DefaultSession {
|
||||||
|
if user != "" {
|
||||||
|
utils.Debug("Creating new session for user: " + user)
|
||||||
|
}
|
||||||
|
|
||||||
// get hostname from request
|
// get hostname from request
|
||||||
hostname := req.Host
|
hostname := req.Host
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package authorizationserver
|
package authorizationserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
)
|
)
|
||||||
|
@ -10,7 +9,6 @@ func authEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
// This context will be passed to all methods.
|
// This context will be passed to all methods.
|
||||||
ctx := req.Context()
|
ctx := req.Context()
|
||||||
|
|
||||||
|
|
||||||
if utils.LoggedInOnly(rw, req) != nil {
|
if utils.LoggedInOnly(rw, req) != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -28,7 +26,7 @@ func authEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
// It will analyze the request and extract important information like scopes, response type and others.
|
// It will analyze the request and extract important information like scopes, response type and others.
|
||||||
ar, err := oauth2.NewAuthorizeRequest(ctx, req)
|
ar, err := oauth2.NewAuthorizeRequest(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error occurred in NewAuthorizeRequest: %+v", err)
|
utils.Error("Error occurred in NewAuthorizeRequest:", err)
|
||||||
oauth2.WriteAuthorizeError(ctx, rw, ar, err)
|
oauth2.WriteAuthorizeError(ctx, rw, ar, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -41,27 +39,6 @@ func authEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
// Now that the user is authorized, we set up a session:
|
// Now that the user is authorized, we set up a session:
|
||||||
mySessionData := newSession(nickname, req)
|
mySessionData := newSession(nickname, req)
|
||||||
|
|
||||||
// When using the HMACSHA strategy you must use something that implements the HMACSessionContainer.
|
|
||||||
// It brings you the power of overriding the default values.
|
|
||||||
//
|
|
||||||
// mySessionData.HMACSession = &strategy.HMACSession{
|
|
||||||
// AccessTokenExpiry: time.Now().Add(time.Day),
|
|
||||||
// AuthorizeCodeExpiry: time.Now().Add(time.Day),
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
|
|
||||||
// If you're using the JWT strategy, there's currently no distinction between access token and authorize code claims.
|
|
||||||
// Therefore, you both access token and authorize code will have the same "exp" claim. If this is something you
|
|
||||||
// need let us know on github.
|
|
||||||
//
|
|
||||||
// mySessionData.JWTClaims.ExpiresAt = time.Now().Add(time.Day)
|
|
||||||
|
|
||||||
// It's also wise to check the requested scopes, e.g.:
|
|
||||||
// if ar.GetRequestedScopes().Has("admin") {
|
|
||||||
// http.Error(rw, "you're not allowed to do that", http.StatusForbidden)
|
|
||||||
// return
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Now we need to get a response. This is the place where the AuthorizeEndpointHandlers kick in and start processing the request.
|
// Now we need to get a response. This is the place where the AuthorizeEndpointHandlers kick in and start processing the request.
|
||||||
// NewAuthorizeResponse is capable of running multiple response type handlers which in turn enables this library
|
// NewAuthorizeResponse is capable of running multiple response type handlers which in turn enables this library
|
||||||
// to support open id connect.
|
// to support open id connect.
|
||||||
|
@ -69,10 +46,8 @@ func authEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
// Catch any errors, e.g.:
|
// Catch any errors, e.g.:
|
||||||
// * unknown client
|
// * unknown client
|
||||||
// * invalid redirect
|
|
||||||
// * ...
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error occurred in NewAuthorizeResponse: %+v", err)
|
utils.Error("Error occurred in NewAuthorizeResponse:", err)
|
||||||
oauth2.WriteAuthorizeError(ctx, rw, ar, err)
|
oauth2.WriteAuthorizeError(ctx, rw, ar, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,200 +9,35 @@ import (
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
// OpenID Connect Discovery Metadata
|
|
||||||
//
|
|
||||||
// Includes links to several endpoints (for example `/oauth2/token`) and exposes information on supported signature algorithms
|
|
||||||
// among others.
|
|
||||||
//
|
|
||||||
// swagger:model oidcConfiguration
|
|
||||||
type oidcConfiguration struct {
|
type oidcConfiguration struct {
|
||||||
// OpenID Connect Issuer URL
|
|
||||||
//
|
|
||||||
// An URL using the https scheme with no query or fragment component that the OP asserts as its IssuerURL Identifier.
|
|
||||||
// If IssuerURL discovery is supported , this value MUST be identical to the issuer value returned
|
|
||||||
// by WebFinger. This also MUST be identical to the iss Claim value in ID Tokens issued from this IssuerURL.
|
|
||||||
//
|
|
||||||
// required: true
|
|
||||||
// example: https://playground.ory.sh/ory-hydra/public/
|
|
||||||
Issuer string `json:"issuer"`
|
Issuer string `json:"issuer"`
|
||||||
|
|
||||||
// OAuth 2.0 Authorization Endpoint URL
|
|
||||||
//
|
|
||||||
// required: true
|
|
||||||
// example: https://playground.ory.sh/ory-hydra/public/oauth2/auth
|
|
||||||
AuthURL string `json:"authorization_endpoint"`
|
AuthURL string `json:"authorization_endpoint"`
|
||||||
|
|
||||||
// OpenID Connect Dynamic Client Registration Endpoint URL
|
|
||||||
//
|
|
||||||
// example: https://playground.ory.sh/ory-hydra/admin/client
|
|
||||||
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
|
RegistrationEndpoint string `json:"registration_endpoint,omitempty"`
|
||||||
|
|
||||||
// OAuth 2.0 Token Endpoint URL
|
|
||||||
//
|
|
||||||
// required: true
|
|
||||||
// example: https://playground.ory.sh/ory-hydra/public/oauth2/token
|
|
||||||
TokenURL string `json:"token_endpoint"`
|
TokenURL string `json:"token_endpoint"`
|
||||||
|
|
||||||
// OpenID Connect Well-Known JSON Web Keys URL
|
|
||||||
//
|
|
||||||
// URL of the OP's JSON Web Key Set [JWK] document. This contains the signing key(s) the RP uses to validate
|
|
||||||
// signatures from the OP. The JWK Set MAY also contain the Server's encryption key(s), which are used by RPs
|
|
||||||
// to encrypt requests to the Server. When both signing and encryption keys are made available, a use (Key Use)
|
|
||||||
// parameter value is REQUIRED for all keys in the referenced JWK Set to indicate each key's intended usage.
|
|
||||||
// Although some algorithms allow the same key to be used for both signatures and encryption, doing so is
|
|
||||||
// NOT RECOMMENDED, as it is less secure. The JWK x5c parameter MAY be used to provide X.509 representations of
|
|
||||||
// keys provided. When used, the bare key values MUST still be present and MUST match those in the certificate.
|
|
||||||
//
|
|
||||||
// required: true
|
|
||||||
// example: https://{slug}.projects.oryapis.com/.well-known/jwks.json
|
|
||||||
JWKsURI string `json:"jwks_uri"`
|
JWKsURI string `json:"jwks_uri"`
|
||||||
|
|
||||||
// OpenID Connect Supported Subject Types
|
|
||||||
//
|
|
||||||
// JSON array containing a list of the Subject Identifier types that this OP supports. Valid types include
|
|
||||||
// pairwise and public.
|
|
||||||
//
|
|
||||||
// required: true
|
|
||||||
// example:
|
|
||||||
// - public
|
|
||||||
// - pairwise
|
|
||||||
SubjectTypes []string `json:"subject_types_supported"`
|
SubjectTypes []string `json:"subject_types_supported"`
|
||||||
|
|
||||||
// OAuth 2.0 Supported Response Types
|
|
||||||
//
|
|
||||||
// JSON array containing a list of the OAuth 2.0 response_type values that this OP supports. Dynamic OpenID
|
|
||||||
// Providers MUST support the code, id_token, and the token id_token Response Type values.
|
|
||||||
//
|
|
||||||
// required: true
|
|
||||||
ResponseTypes []string `json:"response_types_supported"`
|
ResponseTypes []string `json:"response_types_supported"`
|
||||||
|
|
||||||
// OpenID Connect Supported Claims
|
|
||||||
//
|
|
||||||
// JSON array containing a list of the Claim Names of the Claims that the OpenID Provider MAY be able to supply
|
|
||||||
// values for. Note that for privacy or other reasons, this might not be an exhaustive list.
|
|
||||||
ClaimsSupported []string `json:"claims_supported"`
|
ClaimsSupported []string `json:"claims_supported"`
|
||||||
|
|
||||||
// OAuth 2.0 Supported Grant Types
|
|
||||||
//
|
|
||||||
// JSON array containing a list of the OAuth 2.0 Grant Type values that this OP supports.
|
|
||||||
GrantTypesSupported []string `json:"grant_types_supported"`
|
GrantTypesSupported []string `json:"grant_types_supported"`
|
||||||
|
|
||||||
// OAuth 2.0 Supported Response Modes
|
|
||||||
//
|
|
||||||
// JSON array containing a list of the OAuth 2.0 response_mode values that this OP supports.
|
|
||||||
ResponseModesSupported []string `json:"response_modes_supported"`
|
ResponseModesSupported []string `json:"response_modes_supported"`
|
||||||
|
|
||||||
// OpenID Connect Userinfo URL
|
|
||||||
//
|
|
||||||
// URL of the OP's UserInfo Endpoint.
|
|
||||||
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
UserinfoEndpoint string `json:"userinfo_endpoint"`
|
||||||
|
|
||||||
// OAuth 2.0 Supported Scope Values
|
|
||||||
//
|
|
||||||
// JSON array containing a list of the OAuth 2.0 [RFC6749] scope values that this server supports. The server MUST
|
|
||||||
// support the openid scope value. Servers MAY choose not to advertise some supported scope values even when this parameter is used
|
|
||||||
ScopesSupported []string `json:"scopes_supported"`
|
ScopesSupported []string `json:"scopes_supported"`
|
||||||
|
|
||||||
// OAuth 2.0 Supported Client Authentication Methods
|
|
||||||
//
|
|
||||||
// JSON array containing a list of Client Authentication methods supported by this Token Endpoint. The options are
|
|
||||||
// client_secret_post, client_secret_basic, client_secret_jwt, and private_key_jwt, as described in Section 9 of OpenID Connect Core 1.0
|
|
||||||
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
|
TokenEndpointAuthMethodsSupported []string `json:"token_endpoint_auth_methods_supported"`
|
||||||
|
|
||||||
// OpenID Connect Supported Userinfo Signing Algorithm
|
|
||||||
//
|
|
||||||
// JSON array containing a list of the JWS [JWS] signing algorithms (alg values) [JWA] supported by the UserInfo Endpoint to encode the Claims in a JWT [JWT].
|
|
||||||
UserinfoSigningAlgValuesSupported []string `json:"userinfo_signing_alg_values_supported"`
|
UserinfoSigningAlgValuesSupported []string `json:"userinfo_signing_alg_values_supported"`
|
||||||
|
|
||||||
// OpenID Connect Supported ID Token Signing Algorithms
|
|
||||||
//
|
|
||||||
// JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for the ID Token
|
|
||||||
// to encode the Claims in a JWT.
|
|
||||||
//
|
|
||||||
// required: true
|
|
||||||
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
|
IDTokenSigningAlgValuesSupported []string `json:"id_token_signing_alg_values_supported"`
|
||||||
|
|
||||||
// OpenID Connect Default ID Token Signing Algorithms
|
|
||||||
//
|
|
||||||
// Algorithm used to sign OpenID Connect ID Tokens.
|
|
||||||
//
|
|
||||||
// required: true
|
|
||||||
IDTokenSignedResponseAlg []string `json:"id_token_signed_response_alg"`
|
IDTokenSignedResponseAlg []string `json:"id_token_signed_response_alg"`
|
||||||
|
|
||||||
// OpenID Connect User Userinfo Signing Algorithm
|
|
||||||
//
|
|
||||||
// Algorithm used to sign OpenID Connect Userinfo Responses.
|
|
||||||
//
|
|
||||||
// required: true
|
|
||||||
UserinfoSignedResponseAlg []string `json:"userinfo_signed_response_alg"`
|
UserinfoSignedResponseAlg []string `json:"userinfo_signed_response_alg"`
|
||||||
|
|
||||||
// OpenID Connect Request Parameter Supported
|
|
||||||
//
|
|
||||||
// Boolean value specifying whether the OP supports use of the request parameter, with true indicating support.
|
|
||||||
RequestParameterSupported bool `json:"request_parameter_supported"`
|
RequestParameterSupported bool `json:"request_parameter_supported"`
|
||||||
|
|
||||||
// OpenID Connect Request URI Parameter Supported
|
|
||||||
//
|
|
||||||
// Boolean value specifying whether the OP supports use of the request_uri parameter, with true indicating support.
|
|
||||||
RequestURIParameterSupported bool `json:"request_uri_parameter_supported"`
|
RequestURIParameterSupported bool `json:"request_uri_parameter_supported"`
|
||||||
|
|
||||||
// OpenID Connect Requires Request URI Registration
|
|
||||||
//
|
|
||||||
// Boolean value specifying whether the OP requires any request_uri values used to be pre-registered
|
|
||||||
// using the request_uris registration parameter.
|
|
||||||
RequireRequestURIRegistration bool `json:"require_request_uri_registration"`
|
RequireRequestURIRegistration bool `json:"require_request_uri_registration"`
|
||||||
|
|
||||||
// OpenID Connect Claims Parameter Parameter Supported
|
|
||||||
//
|
|
||||||
// Boolean value specifying whether the OP supports use of the claims parameter, with true indicating support.
|
|
||||||
ClaimsParameterSupported bool `json:"claims_parameter_supported"`
|
ClaimsParameterSupported bool `json:"claims_parameter_supported"`
|
||||||
|
|
||||||
// OAuth 2.0 Token Revocation URL
|
|
||||||
//
|
|
||||||
// URL of the authorization server's OAuth 2.0 revocation endpoint.
|
|
||||||
RevocationEndpoint string `json:"revocation_endpoint"`
|
RevocationEndpoint string `json:"revocation_endpoint"`
|
||||||
|
|
||||||
// OpenID Connect Back-Channel Logout Supported
|
|
||||||
//
|
|
||||||
// Boolean value specifying whether the OP supports back-channel logout, with true indicating support.
|
|
||||||
BackChannelLogoutSupported bool `json:"backchannel_logout_supported"`
|
BackChannelLogoutSupported bool `json:"backchannel_logout_supported"`
|
||||||
|
|
||||||
// OpenID Connect Back-Channel Logout Session Required
|
|
||||||
//
|
|
||||||
// Boolean value specifying whether the OP can pass a sid (session ID) Claim in the Logout Token to identify the RP
|
|
||||||
// session with the OP. If supported, the sid Claim is also included in ID Tokens issued by the OP
|
|
||||||
BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported"`
|
BackChannelLogoutSessionSupported bool `json:"backchannel_logout_session_supported"`
|
||||||
|
|
||||||
// OpenID Connect Front-Channel Logout Supported
|
|
||||||
//
|
|
||||||
// Boolean value specifying whether the OP supports HTTP-based logout, with true indicating support.
|
|
||||||
FrontChannelLogoutSupported bool `json:"frontchannel_logout_supported"`
|
FrontChannelLogoutSupported bool `json:"frontchannel_logout_supported"`
|
||||||
|
|
||||||
// OpenID Connect Front-Channel Logout Session Required
|
|
||||||
//
|
|
||||||
// Boolean value specifying whether the OP can pass iss (issuer) and sid (session ID) query parameters to identify
|
|
||||||
// the RP session with the OP when the frontchannel_logout_uri is used. If supported, the sid Claim is also
|
|
||||||
// included in ID Tokens issued by the OP.
|
|
||||||
FrontChannelLogoutSessionSupported bool `json:"frontchannel_logout_session_supported"`
|
FrontChannelLogoutSessionSupported bool `json:"frontchannel_logout_session_supported"`
|
||||||
|
|
||||||
// OpenID Connect End-Session Endpoint
|
|
||||||
//
|
|
||||||
// URL at the OP to which an RP can perform a redirect to request that the End-User be logged out at the OP.
|
|
||||||
EndSessionEndpoint string `json:"end_session_endpoint"`
|
EndSessionEndpoint string `json:"end_session_endpoint"`
|
||||||
|
|
||||||
// OpenID Connect Supported Request Object Signing Algorithms
|
|
||||||
//
|
|
||||||
// JSON array containing a list of the JWS signing algorithms (alg values) supported by the OP for Request Objects,
|
|
||||||
// which are described in Section 6.1 of OpenID Connect Core 1.0 [OpenID.Core]. These algorithms are used both when
|
|
||||||
// the Request Object is passed by value (using the request parameter) and when it is passed by reference
|
|
||||||
// (using the request_uri parameter).
|
|
||||||
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
|
RequestObjectSigningAlgValuesSupported []string `json:"request_object_signing_alg_values_supported"`
|
||||||
|
|
||||||
// OAuth 2.0 PKCE Supported Code Challenge Methods
|
|
||||||
//
|
|
||||||
// JSON array containing a list of Proof Key for Code Exchange (PKCE) [RFC7636] code challenge methods supported
|
|
||||||
// by this authorization server.
|
|
||||||
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
CodeChallengeMethodsSupported []string `json:"code_challenge_methods_supported"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
package authorizationserver
|
package authorizationserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func introspectionEndpoint(rw http.ResponseWriter, req *http.Request) {
|
func introspectionEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -10,7 +10,7 @@ func introspectionEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
mySessionData := newSession("", req)
|
mySessionData := newSession("", req)
|
||||||
ir, err := oauth2.NewIntrospectionRequest(ctx, req, mySessionData)
|
ir, err := oauth2.NewIntrospectionRequest(ctx, req, mySessionData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error occurred in NewIntrospectionRequest: %+v", err)
|
utils.Error("Error occurred in NewIntrospectionRequest", err)
|
||||||
oauth2.WriteIntrospectionError(ctx, rw, err)
|
oauth2.WriteIntrospectionError(ctx, rw, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
// "fmt"
|
// "fmt"
|
||||||
|
|
||||||
// "github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func tokenEndpoint(rw http.ResponseWriter, req *http.Request) {
|
func tokenEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -46,6 +46,8 @@ func tokenEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
utils.Log("Access token granted to client: " + accessRequest.GetClient().GetID())
|
||||||
|
|
||||||
// All done, send the response.
|
// All done, send the response.
|
||||||
oauth2.WriteAccessResponse(ctx, rw, accessRequest, response)
|
oauth2.WriteAccessResponse(ctx, rw, accessRequest, response)
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,8 @@ func userInfosEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
errorDescription := "Only access tokens are allowed in the authorization header."
|
errorDescription := "Only access tokens are allowed in the authorization header."
|
||||||
rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errorDescription))
|
rw.Header().Set("WWW-Authenticate", fmt.Sprintf(`Bearer error="invalid_token",error_description="%s"`, errorDescription))
|
||||||
// h.r.Writer().WriteErrorCode(w, r, http.StatusUnauthorized, errors.New(errorDescription))
|
// h.r.Writer().WriteErrorCode(w, r, http.StatusUnauthorized, errors.New(errorDescription))
|
||||||
|
utils.Error("UserInfosGet: Only access tokens are allowed in the authorization header", err)
|
||||||
|
utils.HTTPError(rw, "Only access tokens are allowed in the authorization header", http.StatusInternalServerError, "UD001")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +54,7 @@ func userInfosEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
utils.Debug("UserGet: Get user " + nickname)
|
utils.Debug("UserInfosGet: Get user " + nickname)
|
||||||
|
|
||||||
user := utils.User{}
|
user := utils.User{}
|
||||||
|
|
||||||
|
@ -61,7 +63,7 @@ func userInfosEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||||
}).Decode(&user)
|
}).Decode(&user)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Error("UserGet: Error while getting user", err)
|
utils.Error("UserInfosGet: Error while getting user", err)
|
||||||
utils.HTTPError(rw, "User Get Error", http.StatusInternalServerError, "UD001")
|
utils.HTTPError(rw, "User Get Error", http.StatusInternalServerError, "UD001")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
|
"github.com/azukaar/cosmos-server/src/authorizationserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
||||||
|
@ -38,6 +39,8 @@ func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
||||||
utils.SaveConfigTofile(request)
|
utils.SaveConfigTofile(request)
|
||||||
utils.NeedsRestart = true
|
utils.NeedsRestart = true
|
||||||
|
|
||||||
|
authorizationserver.Init()
|
||||||
|
|
||||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||||
"status": "OK",
|
"status": "OK",
|
||||||
})
|
})
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/azukaar/cosmos-server/src/docker"
|
"github.com/azukaar/cosmos-server/src/docker"
|
||||||
"github.com/azukaar/cosmos-server/src/utils"
|
"github.com/azukaar/cosmos-server/src/utils"
|
||||||
|
"github.com/azukaar/cosmos-server/src/authorizationserver"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -36,5 +37,7 @@ func main() {
|
||||||
utils.Log("Docker API version: " + version.APIVersion)
|
utils.Log("Docker API version: " + version.APIVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authorizationserver.Init()
|
||||||
|
|
||||||
StartServer()
|
StartServer()
|
||||||
}
|
}
|
||||||
|
|
|
@ -85,6 +85,7 @@ type Config struct {
|
||||||
ServerCountry string
|
ServerCountry string
|
||||||
RequireMFA bool
|
RequireMFA bool
|
||||||
AutoUpdate bool
|
AutoUpdate bool
|
||||||
|
OpenIDClients []OpenIDClient
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPConfig struct {
|
type HTTPConfig struct {
|
||||||
|
@ -159,4 +160,10 @@ type EmailConfig struct {
|
||||||
Password string
|
Password string
|
||||||
From string
|
From string
|
||||||
UseTLS bool
|
UseTLS bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type OpenIDClient struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Secret string `json:"secret"`
|
||||||
|
Redirect string `json:"redirect"`
|
||||||
}
|
}
|
Loading…
Reference in a new issue