[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 isDark = theme.palette.mode === 'dark';
|
||||
let c = routeImages[route.Mode.toUpperCase()];
|
||||
return <>
|
||||
return c ? <>
|
||||
<Chip
|
||||
icon={<span>{c.icon}</span>}
|
||||
label={c.label}
|
||||
|
@ -56,7 +56,7 @@ export const RouteMode = ({route}) => {
|
|||
alignItems: "right",
|
||||
}}
|
||||
></Chip>
|
||||
</>
|
||||
</> : <></>;
|
||||
}
|
||||
|
||||
export const RouteSecurity = ({route}) => {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// assets
|
||||
import { ProfileOutlined, SettingOutlined, NodeExpandOutlined, AppstoreOutlined} from '@ant-design/icons';
|
||||
import { ProfileOutlined, PicLeftOutlined, SettingOutlined, NodeExpandOutlined, AppstoreOutlined} from '@ant-design/icons';
|
||||
|
||||
// icons
|
||||
const icons = {
|
||||
|
@ -36,6 +36,13 @@ const pages = {
|
|||
url: '/ui/config-users',
|
||||
icon: icons.ProfileOutlined,
|
||||
},
|
||||
{
|
||||
id: 'openid',
|
||||
title: 'OpenID',
|
||||
type: 'item',
|
||||
url: '/ui/openid-manage',
|
||||
icon: PicLeftOutlined,
|
||||
},
|
||||
{
|
||||
id: 'config',
|
||||
title: 'Configuration',
|
||||
|
|
|
@ -7,10 +7,15 @@ import { Grid, Stack, Typography } from '@mui/material';
|
|||
import AuthLogin from './auth-forms/AuthLogin';
|
||||
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 ||================================ //
|
||||
|
||||
const Login = () => (
|
||||
<AuthWrapper>
|
||||
<link rel="openid2.provider openid.server" href={selfHostname + "/oauth2/auth"} />
|
||||
<Grid container spacing={3}>
|
||||
<Grid item xs={12}>
|
||||
<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 [checkedScopes, setCheckedScopes] = useState(["openid"])
|
||||
|
||||
let icon;
|
||||
|
||||
// get hostname from redirect_uri with port
|
||||
const port = new URL(redirect_uri).port
|
||||
const protocol = new URL(redirect_uri).protocol + "//"
|
||||
const appHostname = protocol + (new URL(redirect_uri).hostname) + (port ? ":" + port : "")
|
||||
const icon = getFaviconURL({
|
||||
let port, protocol, appHostname;
|
||||
|
||||
try {
|
||||
port = new URL(redirect_uri).port
|
||||
protocol = new URL(redirect_uri).protocol + "//"
|
||||
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 selfprotocol = new URL(window.location.href).protocol + "//"
|
||||
|
@ -55,7 +63,7 @@ const OpenID = () => {
|
|||
}}>
|
||||
<img src={icon} alt={'icon'} width="64px" />
|
||||
<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 />
|
||||
</div>
|
||||
</Stack>
|
||||
|
@ -84,7 +92,7 @@ const OpenID = () => {
|
|||
opacity: '0.8',
|
||||
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>
|
||||
|
||||
<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 NewDockerService from '../pages/servapps/containers/newService';
|
||||
import NewDockerServiceForm from '../pages/servapps/containers/newServiceForm';
|
||||
import OpenIdList from '../pages/openid/openid-list';
|
||||
|
||||
|
||||
// render - dashboard
|
||||
|
@ -81,6 +82,10 @@ const MainRoutes = {
|
|||
path: '/ui/servapps/containers/:containerName',
|
||||
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",
|
||||
"version": "0.6.0-unstable",
|
||||
"version": "0.6.0-unstable2",
|
||||
"description": "",
|
||||
"main": "test-server.js",
|
||||
"bugs": {
|
||||
|
@ -22,6 +22,8 @@
|
|||
"@testing-library/user-event": "^14.4.3",
|
||||
"@vitejs/plugin-react": "^3.1.0",
|
||||
"apexcharts": "^3.35.5",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"browserslist": "^4.21.7",
|
||||
"formik": "^2.2.9",
|
||||
"framer-motion": "^7.3.6",
|
||||
"history": "^5.3.0",
|
||||
|
@ -55,7 +57,7 @@
|
|||
"scripts": {
|
||||
"client": "vite",
|
||||
"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",
|
||||
"dev": "npm run build && npm run start",
|
||||
"dockerdevbuild": "sh build.sh && docker build -f dockerfile.local --tag cosmos-dev .",
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
package authorizationserver
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"math/rand"
|
||||
"crypto/rsa"
|
||||
"github.com/ory/fosite"
|
||||
"time"
|
||||
"net/http"
|
||||
"os"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
|
||||
|
||||
"github.com/ory/fosite/compose"
|
||||
"github.com/ory/fosite/handler/openid"
|
||||
|
@ -18,6 +21,53 @@ import (
|
|||
"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) {
|
||||
// Set up oauth2 endpoints. You could also use gorilla/mux or any other router.
|
||||
userRouter.HandleFunc("/auth", authEndpoint)
|
||||
|
@ -33,94 +83,8 @@ func RegisterHandlers(wellKnown *mux.Router, userRouter *mux.Router, serverRoute
|
|||
// public endpoints
|
||||
wellKnown.HandleFunc("/openid-configuration", discoverEndpoint)
|
||||
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",
|
||||
// "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
|
||||
|
@ -132,6 +96,10 @@ var oauth2 = compose.ComposeAllEnabled(config, store, AuthPrivateKey)
|
|||
//
|
||||
// session = new(fosite.DefaultSession)
|
||||
func newSession(user string, req *http.Request) *openid.DefaultSession {
|
||||
if user != "" {
|
||||
utils.Debug("Creating new session for user: " + user)
|
||||
}
|
||||
|
||||
// get hostname from request
|
||||
hostname := req.Host
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package authorizationserver
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"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.
|
||||
ctx := req.Context()
|
||||
|
||||
|
||||
if utils.LoggedInOnly(rw, req) != nil {
|
||||
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.
|
||||
ar, err := oauth2.NewAuthorizeRequest(ctx, req)
|
||||
if err != nil {
|
||||
log.Printf("Error occurred in NewAuthorizeRequest: %+v", err)
|
||||
utils.Error("Error occurred in NewAuthorizeRequest:", err)
|
||||
oauth2.WriteAuthorizeError(ctx, rw, ar, err)
|
||||
return
|
||||
}
|
||||
|
@ -41,27 +39,6 @@ func authEndpoint(rw http.ResponseWriter, req *http.Request) {
|
|||
// Now that the user is authorized, we set up a session:
|
||||
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.
|
||||
// NewAuthorizeResponse is capable of running multiple response type handlers which in turn enables this library
|
||||
// to support open id connect.
|
||||
|
@ -69,10 +46,8 @@ func authEndpoint(rw http.ResponseWriter, req *http.Request) {
|
|||
|
||||
// Catch any errors, e.g.:
|
||||
// * unknown client
|
||||
// * invalid redirect
|
||||
// * ...
|
||||
if err != nil {
|
||||
log.Printf("Error occurred in NewAuthorizeResponse: %+v", err)
|
||||
utils.Error("Error occurred in NewAuthorizeResponse:", err)
|
||||
oauth2.WriteAuthorizeError(ctx, rw, ar, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,200 +9,35 @@ import (
|
|||
"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 {
|
||||
// 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"`
|
||||
|
||||
// OAuth 2.0 Authorization Endpoint URL
|
||||
//
|
||||
// required: true
|
||||
// example: https://playground.ory.sh/ory-hydra/public/oauth2/auth
|
||||
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"`
|
||||
|
||||
// OAuth 2.0 Token Endpoint URL
|
||||
//
|
||||
// required: true
|
||||
// example: https://playground.ory.sh/ory-hydra/public/oauth2/token
|
||||
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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// OpenID Connect Userinfo URL
|
||||
//
|
||||
// URL of the OP's 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// OpenID Connect User Userinfo Signing Algorithm
|
||||
//
|
||||
// Algorithm used to sign OpenID Connect Userinfo Responses.
|
||||
//
|
||||
// required: true
|
||||
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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// OAuth 2.0 Token Revocation URL
|
||||
//
|
||||
// URL of the authorization server's OAuth 2.0 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
|
||||
// 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"`
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package authorizationserver
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
func introspectionEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||
|
@ -10,7 +10,7 @@ func introspectionEndpoint(rw http.ResponseWriter, req *http.Request) {
|
|||
mySessionData := newSession("", req)
|
||||
ir, err := oauth2.NewIntrospectionRequest(ctx, req, mySessionData)
|
||||
if err != nil {
|
||||
log.Printf("Error occurred in NewIntrospectionRequest: %+v", err)
|
||||
utils.Error("Error occurred in NewIntrospectionRequest", err)
|
||||
oauth2.WriteIntrospectionError(ctx, rw, err)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"net/http"
|
||||
// "fmt"
|
||||
|
||||
// "github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
)
|
||||
|
||||
func tokenEndpoint(rw http.ResponseWriter, req *http.Request) {
|
||||
|
@ -46,6 +46,8 @@ func tokenEndpoint(rw http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
utils.Log("Access token granted to client: " + accessRequest.GetClient().GetID())
|
||||
|
||||
// All done, send the 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."
|
||||
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))
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -52,7 +54,7 @@ func userInfosEndpoint(rw http.ResponseWriter, req *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
utils.Debug("UserGet: Get user " + nickname)
|
||||
utils.Debug("UserInfosGet: Get user " + nickname)
|
||||
|
||||
user := utils.User{}
|
||||
|
||||
|
@ -61,7 +63,7 @@ func userInfosEndpoint(rw http.ResponseWriter, req *http.Request) {
|
|||
}).Decode(&user)
|
||||
|
||||
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")
|
||||
return
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"net/http"
|
||||
"encoding/json"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/azukaar/cosmos-server/src/authorizationserver"
|
||||
)
|
||||
|
||||
func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
||||
|
@ -38,6 +39,8 @@ func ConfigApiSet(w http.ResponseWriter, req *http.Request) {
|
|||
utils.SaveConfigTofile(request)
|
||||
utils.NeedsRestart = true
|
||||
|
||||
authorizationserver.Init()
|
||||
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
})
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/azukaar/cosmos-server/src/docker"
|
||||
"github.com/azukaar/cosmos-server/src/utils"
|
||||
"github.com/azukaar/cosmos-server/src/authorizationserver"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -36,5 +37,7 @@ func main() {
|
|||
utils.Log("Docker API version: " + version.APIVersion)
|
||||
}
|
||||
|
||||
authorizationserver.Init()
|
||||
|
||||
StartServer()
|
||||
}
|
||||
|
|
|
@ -85,6 +85,7 @@ type Config struct {
|
|||
ServerCountry string
|
||||
RequireMFA bool
|
||||
AutoUpdate bool
|
||||
OpenIDClients []OpenIDClient
|
||||
}
|
||||
|
||||
type HTTPConfig struct {
|
||||
|
@ -160,3 +161,9 @@ type EmailConfig struct {
|
|||
From string
|
||||
UseTLS bool
|
||||
}
|
||||
|
||||
type OpenIDClient struct {
|
||||
ID string `json:"id"`
|
||||
Secret string `json:"secret"`
|
||||
Redirect string `json:"redirect"`
|
||||
}
|
Loading…
Reference in a new issue