Merge pull request #104 from jyash97/yash/dashboard
feat: setup authorizer dashboard
This commit is contained in:
commit
7ce96367a3
7
.github/workflows/release.yaml
vendored
7
.github/workflows/release.yaml
vendored
|
@ -23,7 +23,8 @@ jobs:
|
||||||
sudo mv github-assets-uploader /usr/sbin/ && \
|
sudo mv github-assets-uploader /usr/sbin/ && \
|
||||||
sudo rm -f github-assets-uploader.tar.gz && \
|
sudo rm -f github-assets-uploader.tar.gz && \
|
||||||
github-assets-uploader -version && \
|
github-assets-uploader -version && \
|
||||||
make build-app
|
make build-app && \
|
||||||
|
make build-dashboard
|
||||||
- name: Print Go paths
|
- name: Print Go paths
|
||||||
run: whereis go
|
run: whereis go
|
||||||
- name: Print Go Version
|
- name: Print Go Version
|
||||||
|
@ -37,12 +38,12 @@ jobs:
|
||||||
make clean && \
|
make clean && \
|
||||||
CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
|
CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
|
||||||
mv build/server build/server.exe && \
|
mv build/server build/server.exe && \
|
||||||
zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates
|
zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates dashboard/build
|
||||||
- name: Package files for linux
|
- name: Package files for linux
|
||||||
run: |
|
run: |
|
||||||
make clean && \
|
make clean && \
|
||||||
CGO_ENABLED=1 make && \
|
CGO_ENABLED=1 make && \
|
||||||
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates
|
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates dashboard/build
|
||||||
- name: Upload assets
|
- name: Upload assets
|
||||||
run: |
|
run: |
|
||||||
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \
|
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \
|
||||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -3,6 +3,8 @@ server/.env
|
||||||
data
|
data
|
||||||
app/node_modules
|
app/node_modules
|
||||||
app/build
|
app/build
|
||||||
|
dashboard/node_modules
|
||||||
|
dashboard/build
|
||||||
build
|
build
|
||||||
.env
|
.env
|
||||||
data.db
|
data.db
|
||||||
|
|
|
@ -14,14 +14,17 @@ RUN apk add build-base &&\
|
||||||
FROM node:17-alpine3.12 as node-builder
|
FROM node:17-alpine3.12 as node-builder
|
||||||
WORKDIR /authorizer
|
WORKDIR /authorizer
|
||||||
COPY app app
|
COPY app app
|
||||||
|
COPY dashboard dashboard
|
||||||
COPY Makefile .
|
COPY Makefile .
|
||||||
RUN apk add build-base &&\
|
RUN apk add build-base &&\
|
||||||
make build-app
|
make build-app && \
|
||||||
|
make build-dashboard
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
WORKDIR /root/
|
WORKDIR /root/
|
||||||
RUN mkdir app
|
RUN mkdir app dashboard
|
||||||
COPY --from=node-builder /authorizer/app/build app/build
|
COPY --from=node-builder /authorizer/app/build app/build
|
||||||
|
COPY --from=node-builder /authorizer/dashboard/build dashboard/build
|
||||||
COPY --from=go-builder /authorizer/build build
|
COPY --from=go-builder /authorizer/build build
|
||||||
COPY templates templates
|
COPY templates templates
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
2
Makefile
2
Makefile
|
@ -5,6 +5,8 @@ cmd:
|
||||||
cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server'
|
cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server'
|
||||||
build-app:
|
build-app:
|
||||||
cd app && npm i && npm run build
|
cd app && npm i && npm run build
|
||||||
|
build-dashboard:
|
||||||
|
cd dashboard && npm i && npm run build
|
||||||
clean:
|
clean:
|
||||||
rm -rf build
|
rm -rf build
|
||||||
test:
|
test:
|
||||||
|
|
|
@ -1,3 +1,14 @@
|
||||||
# Authorizer APP
|
# Authorizer APP
|
||||||
|
|
||||||
App that can be used as login wall for your any application in combination with @authorizerdev/@authorizer.js
|
App that can be used as login wall for your any application in combination with @authorizerdev/@authorizer.js
|
||||||
|
|
||||||
|
### Getting started
|
||||||
|
|
||||||
|
**Setting up locally**
|
||||||
|
|
||||||
|
- `cd app`
|
||||||
|
- `npm start`
|
||||||
|
|
||||||
|
**Creating production build**
|
||||||
|
|
||||||
|
- `make build-app`
|
||||||
|
|
12
dashboard/README.md
Normal file
12
dashboard/README.md
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# Authorizer dashboard
|
||||||
|
|
||||||
|
### Getting started
|
||||||
|
|
||||||
|
**Setting up locally**
|
||||||
|
|
||||||
|
- `cd dashboard`
|
||||||
|
- `npm start`
|
||||||
|
|
||||||
|
**Creating production build**
|
||||||
|
|
||||||
|
- `make build-dashboard`
|
12
dashboard/esbuild.config.js
Normal file
12
dashboard/esbuild.config.js
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
const __is_prod__ = process.env.NODE_ENV === 'production';
|
||||||
|
require('esbuild').build({
|
||||||
|
entryPoints: ['src/index.tsx'],
|
||||||
|
chunkNames: '[name]-[hash]',
|
||||||
|
bundle: true,
|
||||||
|
minify: __is_prod__,
|
||||||
|
outdir: 'build',
|
||||||
|
splitting: true,
|
||||||
|
format: 'esm',
|
||||||
|
watch: !__is_prod__,
|
||||||
|
logLevel: 'info',
|
||||||
|
});
|
1682
dashboard/package-lock.json
generated
Normal file
1682
dashboard/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
dashboard/package.json
Normal file
30
dashboard/package.json
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
{
|
||||||
|
"name": "dashboard",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
||||||
|
"start": "NODE_ENV=development node ./esbuild.config.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Lakhan Samani",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/react": "^1.7.3",
|
||||||
|
"@emotion/react": "^11.7.1",
|
||||||
|
"@emotion/styled": "^11.6.0",
|
||||||
|
"@types/react": "^17.0.38",
|
||||||
|
"@types/react-dom": "^17.0.11",
|
||||||
|
"@types/react-router-dom": "^5.3.2",
|
||||||
|
"esbuild": "^0.14.9",
|
||||||
|
"framer-motion": "^5.5.5",
|
||||||
|
"graphql": "^16.2.0",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"react-icons": "^4.3.1",
|
||||||
|
"react-router-dom": "^6.2.1",
|
||||||
|
"typescript": "^4.5.4",
|
||||||
|
"urql": "^2.0.6"
|
||||||
|
}
|
||||||
|
}
|
44
dashboard/src/App.tsx
Normal file
44
dashboard/src/App.tsx
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import * as React from "react";
|
||||||
|
import { ChakraProvider, extendTheme } from "@chakra-ui/react";
|
||||||
|
import { BrowserRouter } from "react-router-dom";
|
||||||
|
import { createClient, Provider } from "urql";
|
||||||
|
import {AppRoutes} from './routes'
|
||||||
|
import { AuthContainer } from "./containers/AuthContainer";
|
||||||
|
|
||||||
|
const queryClient = createClient({
|
||||||
|
url: "/graphql",
|
||||||
|
fetchOptions: () => {
|
||||||
|
return {
|
||||||
|
credentials: "include",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const theme = extendTheme({
|
||||||
|
styles: {
|
||||||
|
global: {
|
||||||
|
"html, body, #root": {
|
||||||
|
height: "100%",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
colors: {
|
||||||
|
blue: {
|
||||||
|
500: "rgb(59,130,246)",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default function App() {
|
||||||
|
return (
|
||||||
|
<ChakraProvider theme={theme}>
|
||||||
|
<Provider value={queryClient}>
|
||||||
|
<BrowserRouter basename="/dashboard">
|
||||||
|
<AuthContainer>
|
||||||
|
<AppRoutes />
|
||||||
|
</AuthContainer>
|
||||||
|
</BrowserRouter>
|
||||||
|
</Provider>
|
||||||
|
</ChakraProvider>
|
||||||
|
);
|
||||||
|
}
|
54
dashboard/src/components/Sidebar.tsx
Normal file
54
dashboard/src/components/Sidebar.tsx
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
import { Box, Image, Link, Text } from "@chakra-ui/react";
|
||||||
|
import { NavLink, useLocation } from "react-router-dom";
|
||||||
|
import React from "react";
|
||||||
|
import { LOGO_URL } from "../constants";
|
||||||
|
|
||||||
|
const routes = [
|
||||||
|
{
|
||||||
|
route: "/users",
|
||||||
|
name: "Users",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
route: "/settings",
|
||||||
|
name: "Settings",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const Sidebar = () => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
return (
|
||||||
|
<Box as="nav" h="100%">
|
||||||
|
<NavLink to="/">
|
||||||
|
<Box d="flex" alignItems="center" p="4" mt="4" mb="10">
|
||||||
|
<Image w="8" src={LOGO_URL} alt="" />
|
||||||
|
<Text
|
||||||
|
color="white"
|
||||||
|
casing="uppercase"
|
||||||
|
fontSize="1xl"
|
||||||
|
ml="3"
|
||||||
|
letterSpacing="1.5px"
|
||||||
|
>
|
||||||
|
Authorizer
|
||||||
|
</Text>
|
||||||
|
</Box>
|
||||||
|
</NavLink>
|
||||||
|
{routes.map(({ route, name }: any) => (
|
||||||
|
<Link
|
||||||
|
color={pathname === route ? "blue.500" : "white"}
|
||||||
|
transition="all ease-in 0.2s"
|
||||||
|
_hover={{ color: pathname === route ? "blue.200" : "whiteAlpha.700" }}
|
||||||
|
px="4"
|
||||||
|
py="2"
|
||||||
|
bg={pathname === route ? "white" : ""}
|
||||||
|
fontWeight="bold"
|
||||||
|
display="block"
|
||||||
|
as={NavLink}
|
||||||
|
key={name}
|
||||||
|
to={route}
|
||||||
|
>
|
||||||
|
{name}
|
||||||
|
</Link>
|
||||||
|
))}
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
1
dashboard/src/constants.ts
Normal file
1
dashboard/src/constants.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const LOGO_URL = "https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png"
|
37
dashboard/src/containers/AuthContainer.tsx
Normal file
37
dashboard/src/containers/AuthContainer.tsx
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import { Center, Spinner } from "@chakra-ui/react";
|
||||||
|
import React from "react";
|
||||||
|
import { Navigate, Route, Routes } from "react-router-dom";
|
||||||
|
import { useLocation } from "react-router-dom";
|
||||||
|
import { useQuery } from "urql";
|
||||||
|
import { AdminSessionQuery } from "../graphql/queries";
|
||||||
|
import { hasAdminSecret } from "../utils";
|
||||||
|
|
||||||
|
export const AuthContainer = ({ children }: { children: any }) => {
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const isOnboardingComplete = hasAdminSecret();
|
||||||
|
const [result] = useQuery({
|
||||||
|
query: AdminSessionQuery,
|
||||||
|
pause: !isOnboardingComplete,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (result.fetching) {
|
||||||
|
return (
|
||||||
|
<Center>
|
||||||
|
<Spinner />
|
||||||
|
</Center>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
result?.error?.message.includes("unauthorized") &&
|
||||||
|
pathname !== "/login"
|
||||||
|
) {
|
||||||
|
return <Navigate to="login" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isOnboardingComplete && pathname !== "/setup") {
|
||||||
|
return <Navigate to="setup" />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return children;
|
||||||
|
};
|
15
dashboard/src/graphql/mutation/index.ts
Normal file
15
dashboard/src/graphql/mutation/index.ts
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
export const AdminSignup = `
|
||||||
|
mutation adminSignup($secret: String!) {
|
||||||
|
_admin_signup (params: {admin_secret: $secret}) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const AdminLogin = `
|
||||||
|
mutation adminLogin($secret: String!){
|
||||||
|
_admin_login(params: { admin_secret: $secret }) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
7
dashboard/src/graphql/queries/index.ts
Normal file
7
dashboard/src/graphql/queries/index.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
export const AdminSessionQuery = `
|
||||||
|
query {
|
||||||
|
_admin_session{
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
5
dashboard/src/index.tsx
Normal file
5
dashboard/src/index.tsx
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById('root'));
|
29
dashboard/src/layouts/AuthLayout.tsx
Normal file
29
dashboard/src/layouts/AuthLayout.tsx
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
import { Box, Center, Flex, Image, Text } from "@chakra-ui/react";
|
||||||
|
import React from "react";
|
||||||
|
import { LOGO_URL } from "../constants";
|
||||||
|
|
||||||
|
export function AuthLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Flex flexWrap="wrap" h="100%">
|
||||||
|
<Center h="100%" flex="3" bg="blue.500" flexDirection="column">
|
||||||
|
<Image
|
||||||
|
src={LOGO_URL}
|
||||||
|
alt=""
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Text
|
||||||
|
color="white"
|
||||||
|
casing="uppercase"
|
||||||
|
fontSize="3xl"
|
||||||
|
mt="2"
|
||||||
|
letterSpacing="2.25px"
|
||||||
|
>
|
||||||
|
Authorizer
|
||||||
|
</Text>
|
||||||
|
</Center>
|
||||||
|
<Center h="100%" flex="2">
|
||||||
|
{children}
|
||||||
|
</Center>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
14
dashboard/src/layouts/DashboardLayout.tsx
Normal file
14
dashboard/src/layouts/DashboardLayout.tsx
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
import { Box, Flex } from "@chakra-ui/react";
|
||||||
|
import React from "react";
|
||||||
|
import { Sidebar } from "../components/Sidebar";
|
||||||
|
|
||||||
|
export function DashboardLayout({ children }: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<Flex flexWrap="wrap" h="100%">
|
||||||
|
<Box maxW="72" bg="blue.500" flex="1">
|
||||||
|
<Sidebar />
|
||||||
|
</Box>
|
||||||
|
<Box as="main" flex="2" p="10">{children}</Box>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}
|
90
dashboard/src/pages/Auth.tsx
Normal file
90
dashboard/src/pages/Auth.tsx
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
Input,
|
||||||
|
useToast,
|
||||||
|
VStack,
|
||||||
|
} from "@chakra-ui/react";
|
||||||
|
import React, { useEffect } from "react";
|
||||||
|
import { useMutation } from "urql";
|
||||||
|
import { AuthLayout } from "../layouts/AuthLayout";
|
||||||
|
import { AdminLogin, AdminSignup } from "../graphql/mutation";
|
||||||
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
|
export const Auth = () => {
|
||||||
|
const [loginResult, login] = useMutation(AdminLogin);
|
||||||
|
const [signUpResult, signup] = useMutation(AdminSignup);
|
||||||
|
|
||||||
|
const toast = useToast();
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { pathname } = useLocation();
|
||||||
|
const isLogin = pathname === "/login";
|
||||||
|
|
||||||
|
const handleAdminSecret = (e: any) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const formValues = [...e.target.elements].reduce((agg: any, elem: any) => {
|
||||||
|
if (elem.id) {
|
||||||
|
return {
|
||||||
|
...agg,
|
||||||
|
[elem.id]: elem.value,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return agg;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
(isLogin ? login : signup)({
|
||||||
|
secret: formValues["admin-secret"],
|
||||||
|
}).then((res) => {
|
||||||
|
if (!res.error?.name) {
|
||||||
|
navigate("/");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const errors = isLogin ? loginResult.error : signUpResult.error;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (errors?.graphQLErrors) {
|
||||||
|
(errors?.graphQLErrors || []).map((error: any) => {
|
||||||
|
toast({
|
||||||
|
title: error.message,
|
||||||
|
isClosable: true,
|
||||||
|
status: "error",
|
||||||
|
position:"bottom-right"
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}, [errors])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<AuthLayout>
|
||||||
|
<form onSubmit={handleAdminSecret}>
|
||||||
|
<VStack spacing="2.5" justify="space-between">
|
||||||
|
<FormControl isRequired>
|
||||||
|
<FormLabel htmlFor="admin-secret">
|
||||||
|
{isLogin ? "Enter" : "Setup"} Admin Secret
|
||||||
|
</FormLabel>
|
||||||
|
<Input
|
||||||
|
size="lg"
|
||||||
|
id="admin-secret"
|
||||||
|
placeholder="Admin secret"
|
||||||
|
minLength={6}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<Button
|
||||||
|
isLoading={signUpResult.fetching || loginResult.fetching}
|
||||||
|
colorScheme="blue"
|
||||||
|
size="lg"
|
||||||
|
w="100%"
|
||||||
|
d="block"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
{isLogin ? "Login" : "Sign up"}
|
||||||
|
</Button>
|
||||||
|
</VStack>
|
||||||
|
</form>
|
||||||
|
</AuthLayout>
|
||||||
|
);
|
||||||
|
};
|
6
dashboard/src/pages/Home.tsx
Normal file
6
dashboard/src/pages/Home.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { Box } from "@chakra-ui/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function Home() {
|
||||||
|
return <Box>Welcome to Authorizer dashboard!</Box>;
|
||||||
|
}
|
6
dashboard/src/pages/Users.tsx
Normal file
6
dashboard/src/pages/Users.tsx
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
import { Box } from "@chakra-ui/react";
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
export function Users() {
|
||||||
|
return <Box>users</Box>;
|
||||||
|
}
|
26
dashboard/src/routes/index.tsx
Normal file
26
dashboard/src/routes/index.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React from "react";
|
||||||
|
import { Outlet, Route, Routes } from "react-router-dom";
|
||||||
|
import { DashboardLayout } from "../layouts/DashboardLayout";
|
||||||
|
import { Auth } from "../pages/Auth";
|
||||||
|
|
||||||
|
import { Home } from "../pages/Home";
|
||||||
|
import { Users } from "../pages/Users";
|
||||||
|
|
||||||
|
export const AppRoutes = () => {
|
||||||
|
return (
|
||||||
|
<Routes>
|
||||||
|
<Route path="login" element={<Auth />} />
|
||||||
|
<Route path="setup" element={<Auth />} />
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<DashboardLayout>
|
||||||
|
<Outlet />
|
||||||
|
</DashboardLayout>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path="/" element={<Home />} />
|
||||||
|
<Route path="users" element={<Users />} />
|
||||||
|
</Route>
|
||||||
|
</Routes>
|
||||||
|
);
|
||||||
|
};
|
3
dashboard/src/utils/index.ts
Normal file
3
dashboard/src/utils/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export const hasAdminSecret = () => {
|
||||||
|
return (<any>window)["__authorizer__"].isOnboardingCompleted === true
|
||||||
|
}
|
72
dashboard/tsconfig.json
Normal file
72
dashboard/tsconfig.json
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
|
||||||
|
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||||
|
// "lib": ["es2018", "dom"], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
"jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */,
|
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
// "outDir": "./", /* Redirect output structure to the directory. */
|
||||||
|
"rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
VERSION="$1"
|
VERSION="$1"
|
||||||
make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make
|
make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make
|
||||||
FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz
|
FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz
|
||||||
tar cvfz ${FILE_NAME} .env app/build build templates
|
tar cvfz ${FILE_NAME} .env app/build build templates dashboard/build
|
||||||
AUTH="Authorization: token $GITHUB_TOKEN"
|
AUTH="Authorization: token $GITHUB_TOKEN"
|
||||||
RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION})
|
RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION})
|
||||||
echo $RELASE_INFO
|
echo $RELASE_INFO
|
||||||
|
|
27
server/__test__/admin_login_test.go
Normal file
27
server/__test__/admin_login_test.go
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func adminLoginTests(s TestSetup, t *testing.T) {
|
||||||
|
t.Run(`should complete admin login`, func(t *testing.T) {
|
||||||
|
_, ctx := createContext(s)
|
||||||
|
_, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
|
||||||
|
AdminSecret: "admin_test",
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
_, err = resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
|
||||||
|
AdminSecret: constants.EnvData.ADMIN_SECRET,
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
}
|
26
server/__test__/admin_logout_test.go
Normal file
26
server/__test__/admin_logout_test.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func adminLogoutTests(s TestSetup, t *testing.T) {
|
||||||
|
t.Run(`should get admin session`, func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
_, err := resolvers.AdminLogout(ctx)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h))
|
||||||
|
_, err = resolvers.AdminLogout(ctx)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
}
|
28
server/__test__/admin_session_test.go
Normal file
28
server/__test__/admin_session_test.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func adminSessionTests(s TestSetup, t *testing.T) {
|
||||||
|
t.Run(`should get admin session`, func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
_, err := resolvers.AdminSession(ctx)
|
||||||
|
log.Println("error:", err)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h))
|
||||||
|
_, err = resolvers.AdminSession(ctx)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
}
|
31
server/__test__/admin_signup_test.go
Normal file
31
server/__test__/admin_signup_test.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func adminSignupTests(s TestSetup, t *testing.T) {
|
||||||
|
t.Run(`should complete admin login`, func(t *testing.T) {
|
||||||
|
_, ctx := createContext(s)
|
||||||
|
_, err := resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
|
||||||
|
AdminSecret: "admin",
|
||||||
|
})
|
||||||
|
log.Println("err", err)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
// reset env for test to pass
|
||||||
|
constants.EnvData.ADMIN_SECRET = ""
|
||||||
|
|
||||||
|
_, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
|
||||||
|
AdminSecret: uuid.New().String(),
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
}
|
29
server/__test__/config_test.go
Normal file
29
server/__test__/config_test.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func configTests(s TestSetup, t *testing.T) {
|
||||||
|
t.Run(`should get config`, func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
_, err := resolvers.ConfigResolver(ctx)
|
||||||
|
log.Println("error:", err)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h))
|
||||||
|
res, err := resolvers.ConfigResolver(ctx)
|
||||||
|
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, *res.AdminSecret, constants.EnvData.ADMIN_SECRET)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,11 +1,13 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -24,7 +26,10 @@ func deleteUserTest(s TestSetup, t *testing.T) {
|
||||||
})
|
})
|
||||||
assert.NotNil(t, err, "unauthorized")
|
assert.NotNil(t, err, "unauthorized")
|
||||||
|
|
||||||
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
|
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h))
|
||||||
|
|
||||||
_, err = resolvers.DeleteUser(ctx, model.DeleteUserInput{
|
_, err = resolvers.DeleteUser(ctx, model.DeleteUserInput{
|
||||||
Email: email,
|
Email: email,
|
||||||
})
|
})
|
||||||
|
|
|
@ -8,18 +8,18 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestEnvs(t *testing.T) {
|
func TestEnvs(t *testing.T) {
|
||||||
constants.ENV_PATH = "../../.env.sample"
|
constants.EnvData.ENV_PATH = "../../.env.sample"
|
||||||
|
|
||||||
assert.Equal(t, constants.ADMIN_SECRET, "admin")
|
assert.Equal(t, constants.EnvData.ADMIN_SECRET, "admin")
|
||||||
assert.Equal(t, constants.ENV, "production")
|
assert.Equal(t, constants.EnvData.ENV, "production")
|
||||||
assert.False(t, constants.DISABLE_EMAIL_VERIFICATION)
|
assert.False(t, constants.EnvData.DISABLE_EMAIL_VERIFICATION)
|
||||||
assert.False(t, constants.DISABLE_MAGIC_LINK_LOGIN)
|
assert.False(t, constants.EnvData.DISABLE_MAGIC_LINK_LOGIN)
|
||||||
assert.False(t, constants.DISABLE_BASIC_AUTHENTICATION)
|
assert.False(t, constants.EnvData.DISABLE_BASIC_AUTHENTICATION)
|
||||||
assert.Equal(t, constants.JWT_TYPE, "HS256")
|
assert.Equal(t, constants.EnvData.JWT_TYPE, "HS256")
|
||||||
assert.Equal(t, constants.JWT_SECRET, "random_string")
|
assert.Equal(t, constants.EnvData.JWT_SECRET, "random_string")
|
||||||
assert.Equal(t, constants.JWT_ROLE_CLAIM, "role")
|
assert.Equal(t, constants.EnvData.JWT_ROLE_CLAIM, "role")
|
||||||
assert.EqualValues(t, constants.ROLES, []string{"user"})
|
assert.EqualValues(t, constants.EnvData.ROLES, []string{"user"})
|
||||||
assert.EqualValues(t, constants.DEFAULT_ROLES, []string{"user"})
|
assert.EqualValues(t, constants.EnvData.DEFAULT_ROLES, []string{"user"})
|
||||||
assert.EqualValues(t, constants.PROTECTED_ROLES, []string{"admin"})
|
assert.EqualValues(t, constants.EnvData.PROTECTED_ROLES, []string{"admin"})
|
||||||
assert.EqualValues(t, constants.ALLOWED_ORIGINS, []string{"*"})
|
assert.EqualValues(t, constants.EnvData.ALLOWED_ORIGINS, []string{"*"})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/enum"
|
"github.com/authorizerdev/authorizer/server/enum"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
@ -25,7 +27,7 @@ func logoutTests(s TestSetup, t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
token := *verifyRes.AccessToken
|
token := *verifyRes.AccessToken
|
||||||
req.Header.Add("Authorization", "Bearer "+token)
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token))
|
||||||
_, err = resolvers.Logout(ctx)
|
_, err = resolvers.Logout(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
_, err = resolvers.Profile(ctx)
|
_, err = resolvers.Profile(ctx)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/enum"
|
"github.com/authorizerdev/authorizer/server/enum"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
@ -26,7 +28,7 @@ func magicLinkLoginTests(s TestSetup, t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
token := *verifyRes.AccessToken
|
token := *verifyRes.AccessToken
|
||||||
req.Header.Add("Authorization", "Bearer "+token)
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token))
|
||||||
_, err = resolvers.Profile(ctx)
|
_, err = resolvers.Profile(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
@ -12,6 +13,7 @@ func metaTests(s TestSetup, t *testing.T) {
|
||||||
t.Run(`should get meta information`, func(t *testing.T) {
|
t.Run(`should get meta information`, func(t *testing.T) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
meta, err := resolvers.Meta(ctx)
|
meta, err := resolvers.Meta(ctx)
|
||||||
|
log.Println("=> meta:", meta)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.False(t, meta.IsFacebookLoginEnabled)
|
assert.False(t, meta.IsFacebookLoginEnabled)
|
||||||
assert.False(t, meta.IsGoogleLoginEnabled)
|
assert.False(t, meta.IsGoogleLoginEnabled)
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/enum"
|
"github.com/authorizerdev/authorizer/server/enum"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
@ -30,7 +32,7 @@ func profileTests(s TestSetup, t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
token := *verifyRes.AccessToken
|
token := *verifyRes.AccessToken
|
||||||
req.Header.Add("Authorization", "Bearer "+token)
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token))
|
||||||
profileRes, err := resolvers.Profile(ctx)
|
profileRes, err := resolvers.Profile(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"log"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/enum"
|
"github.com/authorizerdev/authorizer/server/enum"
|
||||||
|
"github.com/authorizerdev/authorizer/server/env"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResolvers(t *testing.T) {
|
func TestResolvers(t *testing.T) {
|
||||||
|
@ -16,14 +18,36 @@ func TestResolvers(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for dbType, dbURL := range databases {
|
for dbType, dbURL := range databases {
|
||||||
constants.DATABASE_URL = dbURL
|
constants.EnvData.DATABASE_URL = dbURL
|
||||||
constants.DATABASE_TYPE = dbType
|
constants.EnvData.DATABASE_TYPE = dbType
|
||||||
db.InitDB()
|
db.InitDB()
|
||||||
|
|
||||||
|
// clean the persisted config for test to use fresh config
|
||||||
|
config, err := db.Mgr.GetConfig()
|
||||||
|
if err == nil {
|
||||||
|
config.Config = []byte{}
|
||||||
|
db.Mgr.UpdateConfig(config)
|
||||||
|
}
|
||||||
|
env.PersistEnv()
|
||||||
|
|
||||||
s := testSetup()
|
s := testSetup()
|
||||||
defer s.Server.Close()
|
defer s.Server.Close()
|
||||||
|
|
||||||
|
log.Println("EnvData:", constants.EnvData)
|
||||||
t.Run("should pass tests for "+dbType, func(t *testing.T) {
|
t.Run("should pass tests for "+dbType, func(t *testing.T) {
|
||||||
|
// admin tests
|
||||||
|
adminSignupTests(s, t)
|
||||||
|
verificationRequestsTest(s, t)
|
||||||
|
usersTest(s, t)
|
||||||
|
deleteUserTest(s, t)
|
||||||
|
updateUserTest(s, t)
|
||||||
|
adminLoginTests(s, t)
|
||||||
|
adminLogoutTests(s, t)
|
||||||
|
adminSessionTests(s, t)
|
||||||
|
updateConfigTests(s, t)
|
||||||
|
configTests(s, t)
|
||||||
|
|
||||||
|
// user tests
|
||||||
loginTests(s, t)
|
loginTests(s, t)
|
||||||
signupTests(s, t)
|
signupTests(s, t)
|
||||||
forgotPasswordTest(s, t)
|
forgotPasswordTest(s, t)
|
||||||
|
@ -36,12 +60,6 @@ func TestResolvers(t *testing.T) {
|
||||||
magicLinkLoginTests(s, t)
|
magicLinkLoginTests(s, t)
|
||||||
logoutTests(s, t)
|
logoutTests(s, t)
|
||||||
metaTests(s, t)
|
metaTests(s, t)
|
||||||
|
|
||||||
// admin tests
|
|
||||||
verificationRequestsTest(s, t)
|
|
||||||
usersTest(s, t)
|
|
||||||
deleteUserTest(s, t)
|
|
||||||
updateUserTest(s, t)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/enum"
|
"github.com/authorizerdev/authorizer/server/enum"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
@ -30,7 +32,8 @@ func sessionTests(s TestSetup, t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
token := *verifyRes.AccessToken
|
token := *verifyRes.AccessToken
|
||||||
req.Header.Add("Authorization", "Bearer "+token)
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token))
|
||||||
|
|
||||||
sessionRes, err := resolvers.Session(ctx, []string{})
|
sessionRes, err := resolvers.Session(ctx, []string{})
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
||||||
|
|
|
@ -72,7 +72,8 @@ func testSetup() TestSetup {
|
||||||
Password: "test",
|
Password: "test",
|
||||||
}
|
}
|
||||||
|
|
||||||
constants.ENV_PATH = "../../.env.sample"
|
constants.EnvData.ENV_PATH = "../../.env.sample"
|
||||||
|
|
||||||
env.InitEnv()
|
env.InitEnv()
|
||||||
session.InitSession()
|
session.InitSession()
|
||||||
|
|
||||||
|
|
44
server/__test__/update_config_test.go
Normal file
44
server/__test__/update_config_test.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func updateConfigTests(s TestSetup, t *testing.T) {
|
||||||
|
t.Run(`should update configs`, func(t *testing.T) {
|
||||||
|
req, ctx := createContext(s)
|
||||||
|
originalAppURL := constants.EnvData.APP_URL
|
||||||
|
log.Println("=> originalAppURL:", constants.EnvData.APP_URL)
|
||||||
|
|
||||||
|
data := model.UpdateConfigInput{}
|
||||||
|
_, err := resolvers.UpdateConfigResolver(ctx, data)
|
||||||
|
log.Println("error:", err)
|
||||||
|
assert.NotNil(t, err)
|
||||||
|
|
||||||
|
h, _ := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h))
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h))
|
||||||
|
newURL := "https://test.com"
|
||||||
|
data = model.UpdateConfigInput{
|
||||||
|
AppURL: &newURL,
|
||||||
|
}
|
||||||
|
_, err = resolvers.UpdateConfigResolver(ctx, data)
|
||||||
|
log.Println("error:", err)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
assert.Equal(t, constants.EnvData.APP_URL, newURL)
|
||||||
|
assert.NotEqual(t, constants.EnvData.APP_URL, originalAppURL)
|
||||||
|
data = model.UpdateConfigInput{
|
||||||
|
AppURL: &originalAppURL,
|
||||||
|
}
|
||||||
|
_, err = resolvers.UpdateConfigResolver(ctx, data)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,8 +1,10 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/enum"
|
"github.com/authorizerdev/authorizer/server/enum"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
@ -33,7 +35,7 @@ func updateProfileTests(s TestSetup, t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
token := *verifyRes.AccessToken
|
token := *verifyRes.AccessToken
|
||||||
req.Header.Add("Authorization", "Bearer "+token)
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.COOKIE_NAME, token))
|
||||||
_, err = resolvers.UpdateProfile(ctx, model.UpdateProfileInput{
|
_, err = resolvers.UpdateProfile(ctx, model.UpdateProfileInput{
|
||||||
FamilyName: &fName,
|
FamilyName: &fName,
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -29,7 +31,9 @@ func updateUserTest(s TestSetup, t *testing.T) {
|
||||||
})
|
})
|
||||||
assert.NotNil(t, err, "unauthorized")
|
assert.NotNil(t, err, "unauthorized")
|
||||||
|
|
||||||
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
|
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h))
|
||||||
_, err = resolvers.UpdateUser(ctx, model.UpdateUserInput{
|
_, err = resolvers.UpdateUser(ctx, model.UpdateUserInput{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Roles: newRoles,
|
Roles: newRoles,
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,7 +24,9 @@ func usersTest(s TestSetup, t *testing.T) {
|
||||||
users, err := resolvers.Users(ctx)
|
users, err := resolvers.Users(ctx)
|
||||||
assert.NotNil(t, err, "unauthorized")
|
assert.NotNil(t, err, "unauthorized")
|
||||||
|
|
||||||
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
|
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h))
|
||||||
users, err = resolvers.Users(ctx)
|
users, err = resolvers.Users(ctx)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
rLen := len(users)
|
rLen := len(users)
|
||||||
|
|
|
@ -22,7 +22,7 @@ func TestIsValidEmail(t *testing.T) {
|
||||||
func TestIsValidOrigin(t *testing.T) {
|
func TestIsValidOrigin(t *testing.T) {
|
||||||
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
|
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
|
||||||
// as we trim them off while running the main function
|
// as we trim them off while running the main function
|
||||||
constants.ALLOWED_ORIGINS = []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}
|
constants.EnvData.ALLOWED_ORIGINS = []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}
|
||||||
|
|
||||||
assert.False(t, utils.IsValidOrigin("http://myapp.com"), "it should be invalid origin")
|
assert.False(t, utils.IsValidOrigin("http://myapp.com"), "it should be invalid origin")
|
||||||
assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin")
|
assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin")
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
package test
|
package test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
"github.com/authorizerdev/authorizer/server/resolvers"
|
"github.com/authorizerdev/authorizer/server/resolvers"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -23,7 +25,9 @@ func verificationRequestsTest(s TestSetup, t *testing.T) {
|
||||||
requests, err := resolvers.VerificationRequests(ctx)
|
requests, err := resolvers.VerificationRequests(ctx)
|
||||||
assert.NotNil(t, err, "unauthorizer")
|
assert.NotNil(t, err, "unauthorizer")
|
||||||
|
|
||||||
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
|
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
assert.Nil(t, err)
|
||||||
|
req.Header.Set("Cookie", fmt.Sprintf("%s=%s", constants.EnvData.ADMIN_COOKIE_NAME, h))
|
||||||
requests, err = resolvers.VerificationRequests(ctx)
|
requests, err = resolvers.VerificationRequests(ctx)
|
||||||
|
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
|
|
|
@ -1,51 +1,62 @@
|
||||||
package constants
|
package constants
|
||||||
|
|
||||||
// this constants are configured via env
|
type EnvConst struct {
|
||||||
var (
|
ADMIN_SECRET string
|
||||||
ADMIN_SECRET = ""
|
ENV string
|
||||||
ENV = ""
|
ENV_PATH string
|
||||||
ENV_PATH = ""
|
VERSION string
|
||||||
VERSION = ""
|
DATABASE_TYPE string
|
||||||
DATABASE_TYPE = ""
|
DATABASE_URL string
|
||||||
DATABASE_URL = ""
|
DATABASE_NAME string
|
||||||
DATABASE_NAME = ""
|
SMTP_HOST string
|
||||||
SMTP_HOST = ""
|
SMTP_PORT string
|
||||||
SMTP_PORT = ""
|
SMTP_PASSWORD string
|
||||||
SMTP_USERNAME = ""
|
SMTP_USERNAME string
|
||||||
SMTP_PASSWORD = ""
|
SENDER_EMAIL string
|
||||||
SENDER_EMAIL = ""
|
JWT_TYPE string
|
||||||
JWT_TYPE = ""
|
JWT_SECRET string
|
||||||
JWT_SECRET = ""
|
ALLOWED_ORIGINS []string
|
||||||
ALLOWED_ORIGINS = []string{}
|
AUTHORIZER_URL string
|
||||||
AUTHORIZER_URL = ""
|
APP_URL string
|
||||||
APP_URL = ""
|
PORT string
|
||||||
PORT = ""
|
REDIS_URL string
|
||||||
REDIS_URL = ""
|
COOKIE_NAME string
|
||||||
IS_PROD = false
|
ADMIN_COOKIE_NAME string
|
||||||
COOKIE_NAME = ""
|
RESET_PASSWORD_URL string
|
||||||
RESET_PASSWORD_URL = ""
|
ENCRYPTION_KEY string `json:"-"`
|
||||||
DISABLE_EMAIL_VERIFICATION = false
|
IS_PROD bool
|
||||||
DISABLE_BASIC_AUTHENTICATION = false
|
DISABLE_EMAIL_VERIFICATION bool
|
||||||
DISABLE_MAGIC_LINK_LOGIN = false
|
DISABLE_BASIC_AUTHENTICATION bool
|
||||||
DISABLE_LOGIN_PAGE = false
|
DISABLE_MAGIC_LINK_LOGIN bool
|
||||||
|
DISABLE_LOGIN_PAGE bool
|
||||||
|
|
||||||
// ROLES
|
// ROLES
|
||||||
ROLES = []string{}
|
ROLES []string
|
||||||
PROTECTED_ROLES = []string{}
|
PROTECTED_ROLES []string
|
||||||
DEFAULT_ROLES = []string{}
|
DEFAULT_ROLES []string
|
||||||
JWT_ROLE_CLAIM = "role"
|
JWT_ROLE_CLAIM string
|
||||||
|
|
||||||
// OAuth login
|
// OAuth login
|
||||||
GOOGLE_CLIENT_ID = ""
|
GOOGLE_CLIENT_ID string
|
||||||
GOOGLE_CLIENT_SECRET = ""
|
GOOGLE_CLIENT_SECRET string
|
||||||
GITHUB_CLIENT_ID = ""
|
GITHUB_CLIENT_ID string
|
||||||
GITHUB_CLIENT_SECRET = ""
|
GITHUB_CLIENT_SECRET string
|
||||||
FACEBOOK_CLIENT_ID = ""
|
FACEBOOK_CLIENT_ID string
|
||||||
FACEBOOK_CLIENT_SECRET = ""
|
FACEBOOK_CLIENT_SECRET string
|
||||||
TWITTER_CLIENT_ID = ""
|
|
||||||
TWITTER_CLIENT_SECRET = ""
|
|
||||||
|
|
||||||
// Org envs
|
// Org envs
|
||||||
ORGANIZATION_NAME = "Authorizer"
|
ORGANIZATION_NAME string
|
||||||
ORGANIZATION_LOGO = "https://authorizer.dev/images/logo.png"
|
ORGANIZATION_LOGO string
|
||||||
)
|
}
|
||||||
|
|
||||||
|
var EnvData = EnvConst{
|
||||||
|
ADMIN_COOKIE_NAME: "authorizer-admin",
|
||||||
|
JWT_ROLE_CLAIM: "role",
|
||||||
|
ORGANIZATION_NAME: "Authorizer",
|
||||||
|
ORGANIZATION_LOGO: "https://authorizer.dev/images/logo.png",
|
||||||
|
DISABLE_EMAIL_VERIFICATION: false,
|
||||||
|
DISABLE_BASIC_AUTHENTICATION: false,
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN: false,
|
||||||
|
DISABLE_LOGIN_PAGE: false,
|
||||||
|
IS_PROD: false,
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
func initArangodb() (arangoDriver.Database, error) {
|
func initArangodb() (arangoDriver.Database, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
conn, err := http.NewConnection(http.ConnectionConfig{
|
conn, err := http.NewConnection(http.ConnectionConfig{
|
||||||
Endpoints: []string{constants.DATABASE_URL},
|
Endpoints: []string{constants.EnvData.DATABASE_URL},
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -32,16 +32,16 @@ func initArangodb() (arangoDriver.Database, error) {
|
||||||
|
|
||||||
var arangodb driver.Database
|
var arangodb driver.Database
|
||||||
|
|
||||||
arangodb_exists, err := arangoClient.DatabaseExists(nil, constants.DATABASE_NAME)
|
arangodb_exists, err := arangoClient.DatabaseExists(nil, constants.EnvData.DATABASE_NAME)
|
||||||
|
|
||||||
if arangodb_exists {
|
if arangodb_exists {
|
||||||
log.Println(constants.DATABASE_NAME + " db exists already")
|
log.Println(constants.EnvData.DATABASE_NAME + " db exists already")
|
||||||
arangodb, err = arangoClient.Database(nil, constants.DATABASE_NAME)
|
arangodb, err = arangoClient.Database(nil, constants.EnvData.DATABASE_NAME)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
arangodb, err = arangoClient.CreateDatabase(nil, constants.DATABASE_NAME, nil)
|
arangodb, err = arangoClient.CreateDatabase(nil, constants.EnvData.DATABASE_NAME, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -100,5 +100,15 @@ func initArangodb() (arangoDriver.Database, error) {
|
||||||
Sparse: true,
|
Sparse: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
configCollectionExists, err := arangodb.CollectionExists(ctx, Collections.Config)
|
||||||
|
if configCollectionExists {
|
||||||
|
log.Println(Collections.Config + " collection exists already")
|
||||||
|
} else {
|
||||||
|
_, err = arangodb.CreateCollection(ctx, Collections.Config, nil)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error creating collection("+Collections.Config+"):", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return arangodb, err
|
return arangodb, err
|
||||||
}
|
}
|
||||||
|
|
161
server/db/config.go
Normal file
161
server/db/config.go
Normal file
|
@ -0,0 +1,161 @@
|
||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
arangoDriver "github.com/arangodb/go-driver"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"go.mongodb.org/mongo-driver/bson"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
|
||||||
|
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
|
||||||
|
Config []byte `gorm:"type:text" json:"config" bson:"config"`
|
||||||
|
Hash string `gorm:"type:hash" json:"hash" bson:"hash"`
|
||||||
|
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
|
||||||
|
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddConfig function to add config
|
||||||
|
func (mgr *manager) AddConfig(config Config) (Config, error) {
|
||||||
|
if config.ID == "" {
|
||||||
|
config.ID = uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsORMSupported {
|
||||||
|
// copy id as value for fields required for mongodb & arangodb
|
||||||
|
config.Key = config.ID
|
||||||
|
result := mgr.sqlDB.Create(&config)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
log.Println("error adding config:", result.Error)
|
||||||
|
return config, result.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsArangoDB {
|
||||||
|
config.CreatedAt = time.Now().Unix()
|
||||||
|
config.UpdatedAt = time.Now().Unix()
|
||||||
|
configCollection, _ := mgr.arangodb.Collection(nil, Collections.Config)
|
||||||
|
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(nil), config)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error adding config:", err)
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
config.Key = meta.Key
|
||||||
|
config.ID = meta.ID.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsMongoDB {
|
||||||
|
config.CreatedAt = time.Now().Unix()
|
||||||
|
config.UpdatedAt = time.Now().Unix()
|
||||||
|
config.Key = config.ID
|
||||||
|
configCollection := mgr.mongodb.Collection(Collections.Config, options.Collection())
|
||||||
|
_, err := configCollection.InsertOne(nil, config)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error adding config:", err)
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateConfig function to update config
|
||||||
|
func (mgr *manager) UpdateConfig(config Config) (Config, error) {
|
||||||
|
config.UpdatedAt = time.Now().Unix()
|
||||||
|
|
||||||
|
if IsORMSupported {
|
||||||
|
result := mgr.sqlDB.Save(&config)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
log.Println("error updating config:", result.Error)
|
||||||
|
return config, result.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsArangoDB {
|
||||||
|
collection, _ := mgr.arangodb.Collection(nil, Collections.Config)
|
||||||
|
meta, err := collection.UpdateDocument(nil, config.Key, config)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error updating config:", err)
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Key = meta.Key
|
||||||
|
config.ID = meta.ID.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsMongoDB {
|
||||||
|
configCollection := mgr.mongodb.Collection(Collections.Config, options.Collection())
|
||||||
|
_, err := configCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": config.ID}}, bson.M{"$set": config}, options.MergeUpdateOptions())
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error updating config:", err)
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfig function to get config
|
||||||
|
func (mgr *manager) GetConfig() (Config, error) {
|
||||||
|
var config Config
|
||||||
|
|
||||||
|
if IsORMSupported {
|
||||||
|
result := mgr.sqlDB.First(&config)
|
||||||
|
|
||||||
|
if result.Error != nil {
|
||||||
|
return config, result.Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsArangoDB {
|
||||||
|
query := fmt.Sprintf("FOR d in %s RETURN d", Collections.Config)
|
||||||
|
|
||||||
|
cursor, err := mgr.arangodb.Query(nil, query, nil)
|
||||||
|
if err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
defer cursor.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
if !cursor.HasMore() {
|
||||||
|
if config.Key == "" {
|
||||||
|
return config, fmt.Errorf("config not found")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
_, err := cursor.ReadDocument(nil, &config)
|
||||||
|
if err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if IsMongoDB {
|
||||||
|
configCollection := mgr.mongodb.Collection(Collections.Config, options.Collection())
|
||||||
|
cursor, err := configCollection.Find(nil, bson.M{}, options.Find())
|
||||||
|
if err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
defer cursor.Close(nil)
|
||||||
|
|
||||||
|
for cursor.Next(nil) {
|
||||||
|
err := cursor.Decode(&config)
|
||||||
|
if err != nil {
|
||||||
|
return config, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.ID == "" {
|
||||||
|
return config, fmt.Errorf("config not found")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, nil
|
||||||
|
}
|
|
@ -29,6 +29,9 @@ type Manager interface {
|
||||||
GetVerificationByEmail(email string, identifier string) (VerificationRequest, error)
|
GetVerificationByEmail(email string, identifier string) (VerificationRequest, error)
|
||||||
AddSession(session Session) error
|
AddSession(session Session) error
|
||||||
DeleteUserSession(userId string) error
|
DeleteUserSession(userId string) error
|
||||||
|
AddConfig(config Config) (Config, error)
|
||||||
|
UpdateConfig(config Config) (Config, error)
|
||||||
|
GetConfig() (Config, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type manager struct {
|
type manager struct {
|
||||||
|
@ -42,6 +45,7 @@ type CollectionList struct {
|
||||||
User string
|
User string
|
||||||
VerificationRequest string
|
VerificationRequest string
|
||||||
Session string
|
Session string
|
||||||
|
Config string
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -54,6 +58,7 @@ var (
|
||||||
User: Prefix + "users",
|
User: Prefix + "users",
|
||||||
VerificationRequest: Prefix + "verification_requests",
|
VerificationRequest: Prefix + "verification_requests",
|
||||||
Session: Prefix + "sessions",
|
Session: Prefix + "sessions",
|
||||||
|
Config: Prefix + "config",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -61,9 +66,9 @@ func InitDB() {
|
||||||
var sqlDB *gorm.DB
|
var sqlDB *gorm.DB
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
IsORMSupported = constants.DATABASE_TYPE != enum.Arangodb.String() && constants.DATABASE_TYPE != enum.Mongodb.String()
|
IsORMSupported = constants.EnvData.DATABASE_TYPE != enum.Arangodb.String() && constants.EnvData.DATABASE_TYPE != enum.Mongodb.String()
|
||||||
IsArangoDB = constants.DATABASE_TYPE == enum.Arangodb.String()
|
IsArangoDB = constants.EnvData.DATABASE_TYPE == enum.Arangodb.String()
|
||||||
IsMongoDB = constants.DATABASE_TYPE == enum.Mongodb.String()
|
IsMongoDB = constants.EnvData.DATABASE_TYPE == enum.Mongodb.String()
|
||||||
|
|
||||||
// sql db orm config
|
// sql db orm config
|
||||||
ormConfig := &gorm.Config{
|
ormConfig := &gorm.Config{
|
||||||
|
@ -72,20 +77,20 @@ func InitDB() {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("db type:", constants.DATABASE_TYPE)
|
log.Println("db type:", constants.EnvData.DATABASE_TYPE)
|
||||||
|
|
||||||
switch constants.DATABASE_TYPE {
|
switch constants.EnvData.DATABASE_TYPE {
|
||||||
case enum.Postgres.String():
|
case enum.Postgres.String():
|
||||||
sqlDB, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig)
|
sqlDB, err = gorm.Open(postgres.Open(constants.EnvData.DATABASE_URL), ormConfig)
|
||||||
break
|
break
|
||||||
case enum.Sqlite.String():
|
case enum.Sqlite.String():
|
||||||
sqlDB, err = gorm.Open(sqlite.Open(constants.DATABASE_URL), ormConfig)
|
sqlDB, err = gorm.Open(sqlite.Open(constants.EnvData.DATABASE_URL), ormConfig)
|
||||||
break
|
break
|
||||||
case enum.Mysql.String():
|
case enum.Mysql.String():
|
||||||
sqlDB, err = gorm.Open(mysql.Open(constants.DATABASE_URL), ormConfig)
|
sqlDB, err = gorm.Open(mysql.Open(constants.EnvData.DATABASE_URL), ormConfig)
|
||||||
break
|
break
|
||||||
case enum.SQLServer.String():
|
case enum.SQLServer.String():
|
||||||
sqlDB, err = gorm.Open(sqlserver.Open(constants.DATABASE_URL), ormConfig)
|
sqlDB, err = gorm.Open(sqlserver.Open(constants.EnvData.DATABASE_URL), ormConfig)
|
||||||
break
|
break
|
||||||
case enum.Arangodb.String():
|
case enum.Arangodb.String():
|
||||||
arangodb, err := initArangodb()
|
arangodb, err := initArangodb()
|
||||||
|
@ -118,7 +123,7 @@ func InitDB() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to init sqlDB:", err)
|
log.Fatal("Failed to init sqlDB:", err)
|
||||||
} else {
|
} else {
|
||||||
sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{})
|
sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{}, &Config{})
|
||||||
}
|
}
|
||||||
Mgr = &manager{
|
Mgr = &manager{
|
||||||
sqlDB: sqlDB,
|
sqlDB: sqlDB,
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func initMongodb() (*mongo.Database, error) {
|
func initMongodb() (*mongo.Database, error) {
|
||||||
mongodbOptions := options.Client().ApplyURI(constants.DATABASE_URL)
|
mongodbOptions := options.Client().ApplyURI(constants.EnvData.DATABASE_URL)
|
||||||
maxWait := time.Duration(5 * time.Second)
|
maxWait := time.Duration(5 * time.Second)
|
||||||
mongodbOptions.ConnectTimeout = &maxWait
|
mongodbOptions.ConnectTimeout = &maxWait
|
||||||
mongoClient, err := mongo.NewClient(mongodbOptions)
|
mongoClient, err := mongo.NewClient(mongodbOptions)
|
||||||
|
@ -30,7 +30,7 @@ func initMongodb() (*mongo.Database, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
mongodb := mongoClient.Database(constants.DATABASE_NAME, options.Database())
|
mongodb := mongoClient.Database(constants.EnvData.DATABASE_NAME, options.Database())
|
||||||
|
|
||||||
mongodb.CreateCollection(ctx, Collections.User, options.CreateCollection())
|
mongodb.CreateCollection(ctx, Collections.User, options.CreateCollection())
|
||||||
userCollection := mongodb.Collection(Collections.User, options.Collection())
|
userCollection := mongodb.Collection(Collections.User, options.Collection())
|
||||||
|
@ -73,5 +73,7 @@ func initMongodb() (*mongo.Database, error) {
|
||||||
},
|
},
|
||||||
}, options.CreateIndexes())
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
|
mongodb.CreateCollection(ctx, Collections.Config, options.CreateCollection())
|
||||||
|
|
||||||
return mongodb, nil
|
return mongodb, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ func (mgr *manager) AddUser(user User) (User, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Roles == "" {
|
if user.Roles == "" {
|
||||||
user.Roles = constants.DEFAULT_ROLES[0]
|
user.Roles = constants.EnvData.DEFAULT_ROLES[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if IsORMSupported {
|
if IsORMSupported {
|
||||||
|
|
|
@ -11,13 +11,13 @@ import (
|
||||||
|
|
||||||
func SendMail(to []string, Subject, bodyMessage string) error {
|
func SendMail(to []string, Subject, bodyMessage string) error {
|
||||||
m := gomail.NewMessage()
|
m := gomail.NewMessage()
|
||||||
m.SetHeader("From", constants.SENDER_EMAIL)
|
m.SetHeader("From", constants.EnvData.SENDER_EMAIL)
|
||||||
m.SetHeader("To", to...)
|
m.SetHeader("To", to...)
|
||||||
m.SetHeader("Subject", Subject)
|
m.SetHeader("Subject", Subject)
|
||||||
m.SetBody("text/html", bodyMessage)
|
m.SetBody("text/html", bodyMessage)
|
||||||
port, _ := strconv.Atoi(constants.SMTP_PORT)
|
port, _ := strconv.Atoi(constants.EnvData.SMTP_PORT)
|
||||||
d := gomail.NewDialer(constants.SMTP_HOST, port, constants.SMTP_USERNAME, constants.SMTP_PASSWORD)
|
d := gomail.NewDialer(constants.EnvData.SMTP_HOST, port, constants.EnvData.SMTP_USERNAME, constants.EnvData.SMTP_PASSWORD)
|
||||||
if constants.ENV == "development" {
|
if constants.EnvData.ENV == "development" {
|
||||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
}
|
}
|
||||||
if err := d.DialAndSend(m); err != nil {
|
if err := d.DialAndSend(m); err != nil {
|
||||||
|
|
235
server/env/env.go
vendored
235
server/env/env.go
vendored
|
@ -7,6 +7,7 @@ import (
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -20,167 +21,173 @@ var (
|
||||||
|
|
||||||
// InitEnv -> to initialize env and through error if required env are not present
|
// InitEnv -> to initialize env and through error if required env are not present
|
||||||
func InitEnv() {
|
func InitEnv() {
|
||||||
if constants.ENV_PATH == "" {
|
if constants.EnvData.ENV_PATH == "" {
|
||||||
constants.ENV_PATH = `.env`
|
constants.EnvData.ENV_PATH = `.env`
|
||||||
}
|
}
|
||||||
|
|
||||||
if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" {
|
if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" {
|
||||||
constants.ENV_PATH = *ARG_ENV_FILE
|
constants.EnvData.ENV_PATH = *ARG_ENV_FILE
|
||||||
}
|
}
|
||||||
|
|
||||||
err := godotenv.Load(constants.ENV_PATH)
|
err := godotenv.Load(constants.EnvData.ENV_PATH)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("error loading %s file", constants.ENV_PATH)
|
log.Printf("error loading %s file", constants.EnvData.ENV_PATH)
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.ADMIN_SECRET == "" {
|
if constants.EnvData.ADMIN_SECRET == "" {
|
||||||
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
|
constants.EnvData.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
|
||||||
if constants.ADMIN_SECRET == "" {
|
|
||||||
panic("root admin secret is required")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.ENV == "" {
|
if constants.EnvData.DATABASE_TYPE == "" {
|
||||||
constants.ENV = os.Getenv("ENV")
|
constants.EnvData.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
|
||||||
if constants.ENV == "" {
|
log.Println(constants.EnvData.DATABASE_TYPE)
|
||||||
constants.ENV = "production"
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.ENV == "production" {
|
|
||||||
constants.IS_PROD = true
|
|
||||||
os.Setenv("GIN_MODE", "release")
|
|
||||||
} else {
|
|
||||||
constants.IS_PROD = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.DATABASE_TYPE == "" {
|
|
||||||
constants.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
|
|
||||||
log.Println(constants.DATABASE_TYPE)
|
|
||||||
|
|
||||||
if ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" {
|
if ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" {
|
||||||
constants.DATABASE_TYPE = *ARG_DB_TYPE
|
constants.EnvData.DATABASE_TYPE = *ARG_DB_TYPE
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.DATABASE_TYPE == "" {
|
if constants.EnvData.DATABASE_TYPE == "" {
|
||||||
panic("DATABASE_TYPE is required")
|
panic("DATABASE_TYPE is required")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.DATABASE_URL == "" {
|
if constants.EnvData.DATABASE_URL == "" {
|
||||||
constants.DATABASE_URL = os.Getenv("DATABASE_URL")
|
constants.EnvData.DATABASE_URL = os.Getenv("DATABASE_URL")
|
||||||
|
|
||||||
if ARG_DB_URL != nil && *ARG_DB_URL != "" {
|
if ARG_DB_URL != nil && *ARG_DB_URL != "" {
|
||||||
constants.DATABASE_URL = *ARG_DB_URL
|
constants.EnvData.DATABASE_URL = *ARG_DB_URL
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.DATABASE_URL == "" {
|
if constants.EnvData.DATABASE_URL == "" {
|
||||||
panic("DATABASE_URL is required")
|
panic("DATABASE_URL is required")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.DATABASE_NAME == "" {
|
if constants.EnvData.DATABASE_NAME == "" {
|
||||||
constants.DATABASE_NAME = os.Getenv("DATABASE_NAME")
|
constants.EnvData.DATABASE_NAME = os.Getenv("DATABASE_NAME")
|
||||||
if constants.DATABASE_NAME == "" {
|
if constants.EnvData.DATABASE_NAME == "" {
|
||||||
constants.DATABASE_NAME = "authorizer"
|
constants.EnvData.DATABASE_NAME = "authorizer"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.SMTP_HOST == "" {
|
if constants.EnvData.ENV == "" {
|
||||||
constants.SMTP_HOST = os.Getenv("SMTP_HOST")
|
constants.EnvData.ENV = os.Getenv("ENV")
|
||||||
}
|
if constants.EnvData.ENV == "" {
|
||||||
|
constants.EnvData.ENV = "production"
|
||||||
|
}
|
||||||
|
|
||||||
if constants.SMTP_PORT == "" {
|
if constants.EnvData.ENV == "production" {
|
||||||
constants.SMTP_PORT = os.Getenv("SMTP_PORT")
|
constants.EnvData.IS_PROD = true
|
||||||
}
|
os.Setenv("GIN_MODE", "release")
|
||||||
|
} else {
|
||||||
if constants.SMTP_USERNAME == "" {
|
constants.EnvData.IS_PROD = false
|
||||||
constants.SMTP_USERNAME = os.Getenv("SMTP_USERNAME")
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.SMTP_PASSWORD == "" {
|
|
||||||
constants.SMTP_PASSWORD = os.Getenv("SMTP_PASSWORD")
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.SENDER_EMAIL == "" {
|
|
||||||
constants.SENDER_EMAIL = os.Getenv("SENDER_EMAIL")
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.JWT_SECRET == "" {
|
|
||||||
constants.JWT_SECRET = os.Getenv("JWT_SECRET")
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.JWT_TYPE == "" {
|
|
||||||
constants.JWT_TYPE = os.Getenv("JWT_TYPE")
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.JWT_ROLE_CLAIM == "" {
|
|
||||||
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
|
|
||||||
|
|
||||||
if constants.JWT_ROLE_CLAIM == "" {
|
|
||||||
constants.JWT_ROLE_CLAIM = "role"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.AUTHORIZER_URL == "" {
|
if constants.EnvData.SMTP_HOST == "" {
|
||||||
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
|
constants.EnvData.SMTP_HOST = os.Getenv("SMTP_HOST")
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.EnvData.SMTP_PORT == "" {
|
||||||
|
constants.EnvData.SMTP_PORT = os.Getenv("SMTP_PORT")
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.EnvData.SMTP_USERNAME == "" {
|
||||||
|
constants.EnvData.SMTP_USERNAME = os.Getenv("SMTP_USERNAME")
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.EnvData.SMTP_PASSWORD == "" {
|
||||||
|
constants.EnvData.SMTP_PASSWORD = os.Getenv("SMTP_PASSWORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.EnvData.SENDER_EMAIL == "" {
|
||||||
|
constants.EnvData.SENDER_EMAIL = os.Getenv("SENDER_EMAIL")
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.EnvData.JWT_SECRET == "" {
|
||||||
|
constants.EnvData.JWT_SECRET = os.Getenv("JWT_SECRET")
|
||||||
|
if constants.EnvData.JWT_SECRET == "" {
|
||||||
|
constants.EnvData.JWT_SECRET = uuid.New().String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.EnvData.JWT_TYPE == "" {
|
||||||
|
constants.EnvData.JWT_TYPE = os.Getenv("JWT_TYPE")
|
||||||
|
if constants.EnvData.JWT_TYPE == "" {
|
||||||
|
constants.EnvData.JWT_TYPE = "HS256"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.EnvData.JWT_ROLE_CLAIM == "" {
|
||||||
|
constants.EnvData.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
|
||||||
|
|
||||||
|
if constants.EnvData.JWT_ROLE_CLAIM == "" {
|
||||||
|
constants.EnvData.JWT_ROLE_CLAIM = "role"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.EnvData.AUTHORIZER_URL == "" {
|
||||||
|
constants.EnvData.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
|
||||||
|
|
||||||
if ARG_AUTHORIZER_URL != nil && *ARG_AUTHORIZER_URL != "" {
|
if ARG_AUTHORIZER_URL != nil && *ARG_AUTHORIZER_URL != "" {
|
||||||
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
|
constants.EnvData.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.PORT == "" {
|
if constants.EnvData.PORT == "" {
|
||||||
constants.PORT = os.Getenv("PORT")
|
constants.EnvData.PORT = os.Getenv("PORT")
|
||||||
if constants.PORT == "" {
|
if constants.EnvData.PORT == "" {
|
||||||
constants.PORT = "8080"
|
constants.EnvData.PORT = "8080"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.REDIS_URL == "" {
|
if constants.EnvData.REDIS_URL == "" {
|
||||||
constants.REDIS_URL = os.Getenv("REDIS_URL")
|
constants.EnvData.REDIS_URL = os.Getenv("REDIS_URL")
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.COOKIE_NAME == "" {
|
if constants.EnvData.COOKIE_NAME == "" {
|
||||||
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME")
|
constants.EnvData.COOKIE_NAME = os.Getenv("COOKIE_NAME")
|
||||||
|
if constants.EnvData.COOKIE_NAME == "" {
|
||||||
|
constants.EnvData.COOKIE_NAME = "authorizer"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.GOOGLE_CLIENT_ID == "" {
|
if constants.EnvData.GOOGLE_CLIENT_ID == "" {
|
||||||
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
|
constants.EnvData.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.GOOGLE_CLIENT_SECRET == "" {
|
if constants.EnvData.GOOGLE_CLIENT_SECRET == "" {
|
||||||
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
|
constants.EnvData.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.GITHUB_CLIENT_ID == "" {
|
if constants.EnvData.GITHUB_CLIENT_ID == "" {
|
||||||
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
|
constants.EnvData.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.GITHUB_CLIENT_SECRET == "" {
|
if constants.EnvData.GITHUB_CLIENT_SECRET == "" {
|
||||||
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
|
constants.EnvData.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.FACEBOOK_CLIENT_ID == "" {
|
if constants.EnvData.FACEBOOK_CLIENT_ID == "" {
|
||||||
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
|
constants.EnvData.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.FACEBOOK_CLIENT_SECRET == "" {
|
if constants.EnvData.FACEBOOK_CLIENT_SECRET == "" {
|
||||||
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
|
constants.EnvData.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.RESET_PASSWORD_URL == "" {
|
if constants.EnvData.RESET_PASSWORD_URL == "" {
|
||||||
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
|
constants.EnvData.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
|
constants.EnvData.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
|
||||||
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
|
constants.EnvData.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
|
||||||
constants.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
|
constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
|
||||||
constants.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true"
|
constants.EnvData.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true"
|
||||||
|
|
||||||
if constants.SMTP_HOST == "" || constants.SMTP_USERNAME == "" || constants.SMTP_PASSWORD == "" || constants.SENDER_EMAIL == "" {
|
if constants.EnvData.SMTP_HOST == "" || constants.EnvData.SMTP_USERNAME == "" || constants.EnvData.SMTP_PASSWORD == "" || constants.EnvData.SENDER_EMAIL == "" {
|
||||||
constants.DISABLE_EMAIL_VERIFICATION = true
|
constants.EnvData.DISABLE_EMAIL_VERIFICATION = true
|
||||||
constants.DISABLE_MAGIC_LINK_LOGIN = true
|
constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true
|
||||||
}
|
}
|
||||||
|
|
||||||
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
|
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
|
||||||
|
@ -209,18 +216,10 @@ func InitEnv() {
|
||||||
allowedOrigins = []string{"*"}
|
allowedOrigins = []string{"*"}
|
||||||
}
|
}
|
||||||
|
|
||||||
constants.ALLOWED_ORIGINS = allowedOrigins
|
constants.EnvData.ALLOWED_ORIGINS = allowedOrigins
|
||||||
|
|
||||||
if constants.JWT_TYPE == "" {
|
if constants.EnvData.DISABLE_EMAIL_VERIFICATION {
|
||||||
constants.JWT_TYPE = "HS256"
|
constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true
|
||||||
}
|
|
||||||
|
|
||||||
if constants.COOKIE_NAME == "" {
|
|
||||||
constants.COOKIE_NAME = "authorizer"
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.DISABLE_EMAIL_VERIFICATION {
|
|
||||||
constants.DISABLE_MAGIC_LINK_LOGIN = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rolesEnv := strings.TrimSpace(os.Getenv("ROLES"))
|
rolesEnv := strings.TrimSpace(os.Getenv("ROLES"))
|
||||||
|
@ -260,19 +259,19 @@ func InitEnv() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRoleSplit) > 0 {
|
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 {
|
||||||
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
|
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
|
||||||
}
|
}
|
||||||
|
|
||||||
constants.ROLES = roles
|
constants.EnvData.ROLES = roles
|
||||||
constants.DEFAULT_ROLES = defaultRoles
|
constants.EnvData.DEFAULT_ROLES = defaultRoles
|
||||||
constants.PROTECTED_ROLES = protectedRoles
|
constants.EnvData.PROTECTED_ROLES = protectedRoles
|
||||||
|
|
||||||
if os.Getenv("ORGANIZATION_NAME") != "" {
|
if os.Getenv("ORGANIZATION_NAME") != "" {
|
||||||
constants.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME")
|
constants.EnvData.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME")
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("ORGANIZATION_LOGO") != "" {
|
if os.Getenv("ORGANIZATION_LOGO") != "" {
|
||||||
constants.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO")
|
constants.EnvData.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
138
server/env/persist_env.go
vendored
Normal file
138
server/env/persist_env.go
vendored
Normal file
|
@ -0,0 +1,138 @@
|
||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
)
|
||||||
|
|
||||||
|
func PersistEnv() error {
|
||||||
|
config, err := db.Mgr.GetConfig()
|
||||||
|
// config not found in db
|
||||||
|
if err != nil {
|
||||||
|
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
|
||||||
|
hash := uuid.New().String()[:36-4]
|
||||||
|
constants.EnvData.ENCRYPTION_KEY = hash
|
||||||
|
encodedHash := utils.EncryptB64(hash)
|
||||||
|
|
||||||
|
configData, err := json.Marshal(constants.EnvData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
encryptedConfig, err := utils.EncryptAES(configData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config = db.Config{
|
||||||
|
Hash: encodedHash,
|
||||||
|
Config: encryptedConfig,
|
||||||
|
}
|
||||||
|
|
||||||
|
db.Mgr.AddConfig(config)
|
||||||
|
} else {
|
||||||
|
// decrypt the config data from db
|
||||||
|
// decryption can be done using the hash stored in db
|
||||||
|
encryptionKey := config.Hash
|
||||||
|
decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
constants.EnvData.ENCRYPTION_KEY = decryptedEncryptionKey
|
||||||
|
decryptedConfigs, err := utils.DecryptAES(config.Config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// temp json to validate with env
|
||||||
|
var jsonData map[string]interface{}
|
||||||
|
|
||||||
|
err = json.Unmarshal(decryptedConfigs, &jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if env is changed via env file or OS env
|
||||||
|
// give that higher preference and update db, but we don't recommend it
|
||||||
|
|
||||||
|
hasChanged := false
|
||||||
|
|
||||||
|
for key, value := range jsonData {
|
||||||
|
fieldType := reflect.TypeOf(value).String()
|
||||||
|
|
||||||
|
// check only for derivative keys
|
||||||
|
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
|
||||||
|
// as we have removed it from json
|
||||||
|
envValue := strings.TrimSpace(os.Getenv(key))
|
||||||
|
|
||||||
|
// env is not empty
|
||||||
|
if envValue != "" {
|
||||||
|
// check the type
|
||||||
|
// currently we have 3 types of env vars: string, bool, []string{}
|
||||||
|
if fieldType == "string" {
|
||||||
|
if value != envValue {
|
||||||
|
jsonData[key] = envValue
|
||||||
|
hasChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldType == "bool" {
|
||||||
|
newValue := envValue == "true"
|
||||||
|
if value != newValue {
|
||||||
|
jsonData[key] = newValue
|
||||||
|
hasChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldType == "[]interface {}" {
|
||||||
|
stringArr := []string{}
|
||||||
|
envStringArr := strings.Split(envValue, ",")
|
||||||
|
for _, v := range value.([]interface{}) {
|
||||||
|
stringArr = append(stringArr, v.(string))
|
||||||
|
}
|
||||||
|
if !utils.IsStringArrayEqual(stringArr, envStringArr) {
|
||||||
|
jsonData[key] = envStringArr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle derivative cases like disabling email verification & magic login
|
||||||
|
// in case SMTP is off but env is set to true
|
||||||
|
if jsonData["SMTP_HOST"] == "" || jsonData["SENDER_EMAIL"] == "" || jsonData["SENDER_PASSWORD"] == "" {
|
||||||
|
if !jsonData["DISABLE_EMAIL_VERIFICATION"].(bool) {
|
||||||
|
jsonData["DISABLE_EMAIL_VERIFICATION"] = true
|
||||||
|
hasChanged = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !jsonData["DISABLE_MAGIC_LINK_LOGIN"].(bool) {
|
||||||
|
jsonData["DISABLE_MAGIC_LINK_LOGIN"] = true
|
||||||
|
hasChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasChanged {
|
||||||
|
encryptedConfig, err := utils.EncryptConfig(jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Config = encryptedConfig
|
||||||
|
_, err = db.Mgr.UpdateConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error updating config:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -20,7 +20,6 @@ require (
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
|
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
|
||||||
github.com/stretchr/testify v1.7.0
|
|
||||||
github.com/ugorji/go v1.2.6 // indirect
|
github.com/ugorji/go v1.2.6 // indirect
|
||||||
github.com/vektah/gqlparser/v2 v2.2.0
|
github.com/vektah/gqlparser/v2 v2.2.0
|
||||||
go.mongodb.org/mongo-driver v1.8.1
|
go.mongodb.org/mongo-driver v1.8.1
|
||||||
|
@ -30,7 +29,7 @@ require (
|
||||||
google.golang.org/appengine v1.6.7 // indirect
|
google.golang.org/appengine v1.6.7 // indirect
|
||||||
google.golang.org/protobuf v1.27.1 // indirect
|
google.golang.org/protobuf v1.27.1 // indirect
|
||||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||||
gopkg.in/mail.v2 v2.3.1 // indirect
|
gopkg.in/mail.v2 v2.3.1
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
gorm.io/driver/mysql v1.2.1
|
gorm.io/driver/mysql v1.2.1
|
||||||
gorm.io/driver/postgres v1.2.3
|
gorm.io/driver/postgres v1.2.3
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,6 +2,14 @@
|
||||||
|
|
||||||
package model
|
package model
|
||||||
|
|
||||||
|
type AdminLoginInput struct {
|
||||||
|
AdminSecret string `json:"admin_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type AdminSignupInput struct {
|
||||||
|
AdminSecret string `json:"admin_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
AccessToken *string `json:"access_token"`
|
AccessToken *string `json:"access_token"`
|
||||||
|
@ -9,6 +17,42 @@ type AuthResponse struct {
|
||||||
User *User `json:"user"`
|
User *User `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
|
DatabaseType *string `json:"DATABASE_TYPE"`
|
||||||
|
DatabaseURL *string `json:"DATABASE_URL"`
|
||||||
|
DatabaseName *string `json:"DATABASE_NAME"`
|
||||||
|
SMTPHost *string `json:"SMTP_HOST"`
|
||||||
|
SMTPPort *string `json:"SMTP_PORT"`
|
||||||
|
SMTPUsername *string `json:"SMTP_USERNAME"`
|
||||||
|
SMTPPassword *string `json:"SMTP_PASSWORD"`
|
||||||
|
SenderEmail *string `json:"SENDER_EMAIL"`
|
||||||
|
JwtType *string `json:"JWT_TYPE"`
|
||||||
|
JwtSecret *string `json:"JWT_SECRET"`
|
||||||
|
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
||||||
|
AuthorizerURL *string `json:"AUTHORIZER_URL"`
|
||||||
|
AppURL *string `json:"APP_URL"`
|
||||||
|
RedisURL *string `json:"REDIS_URL"`
|
||||||
|
CookieName *string `json:"COOKIE_NAME"`
|
||||||
|
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
|
||||||
|
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
|
||||||
|
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
||||||
|
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
||||||
|
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
||||||
|
Roles []string `json:"ROLES"`
|
||||||
|
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
||||||
|
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
||||||
|
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
|
||||||
|
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
|
||||||
|
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
|
||||||
|
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
|
||||||
|
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
|
||||||
|
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
|
||||||
|
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
|
||||||
|
OrganizationName *string `json:"ORGANIZATION_NAME"`
|
||||||
|
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteUserInput struct {
|
type DeleteUserInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
@ -73,6 +117,41 @@ type SignUpInput struct {
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateConfigInput struct {
|
||||||
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
|
DatabaseType *string `json:"DATABASE_TYPE"`
|
||||||
|
DatabaseURL *string `json:"DATABASE_URL"`
|
||||||
|
DatabaseName *string `json:"DATABASE_NAME"`
|
||||||
|
SMTPHost *string `json:"SMTP_HOST"`
|
||||||
|
SMTPPort *string `json:"SMTP_PORT"`
|
||||||
|
SenderEmail *string `json:"SENDER_EMAIL"`
|
||||||
|
SenderPassword *string `json:"SENDER_PASSWORD"`
|
||||||
|
JwtType *string `json:"JWT_TYPE"`
|
||||||
|
JwtSecret *string `json:"JWT_SECRET"`
|
||||||
|
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
||||||
|
AuthorizerURL *string `json:"AUTHORIZER_URL"`
|
||||||
|
AppURL *string `json:"APP_URL"`
|
||||||
|
RedisURL *string `json:"REDIS_URL"`
|
||||||
|
CookieName *string `json:"COOKIE_NAME"`
|
||||||
|
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
|
||||||
|
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
|
||||||
|
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
||||||
|
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
||||||
|
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
||||||
|
Roles []string `json:"ROLES"`
|
||||||
|
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
||||||
|
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
||||||
|
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
|
||||||
|
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
|
||||||
|
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
|
||||||
|
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
|
||||||
|
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
|
||||||
|
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
|
||||||
|
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
|
||||||
|
OrganizationName *string `json:"ORGANIZATION_NAME"`
|
||||||
|
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateProfileInput struct {
|
type UpdateProfileInput struct {
|
||||||
OldPassword *string `json:"old_password"`
|
OldPassword *string `json:"old_password"`
|
||||||
NewPassword *string `json:"new_password"`
|
NewPassword *string `json:"new_password"`
|
||||||
|
|
|
@ -62,6 +62,85 @@ type Response {
|
||||||
message: String!
|
message: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Config {
|
||||||
|
ADMIN_SECRET: String
|
||||||
|
DATABASE_TYPE: String
|
||||||
|
DATABASE_URL: String
|
||||||
|
DATABASE_NAME: String
|
||||||
|
SMTP_HOST: String
|
||||||
|
SMTP_PORT: String
|
||||||
|
SMTP_USERNAME: String
|
||||||
|
SMTP_PASSWORD: String
|
||||||
|
SENDER_EMAIL: String
|
||||||
|
JWT_TYPE: String
|
||||||
|
JWT_SECRET: String
|
||||||
|
ALLOWED_ORIGINS: [String!]
|
||||||
|
AUTHORIZER_URL: String
|
||||||
|
APP_URL: String
|
||||||
|
REDIS_URL: String
|
||||||
|
COOKIE_NAME: String
|
||||||
|
RESET_PASSWORD_URL: String
|
||||||
|
DISABLE_EMAIL_VERIFICATION: Boolean
|
||||||
|
DISABLE_BASIC_AUTHENTICATION: Boolean
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN: Boolean
|
||||||
|
DISABLE_LOGIN_PAGE: Boolean
|
||||||
|
ROLES: [String!]
|
||||||
|
PROTECTED_ROLES: [String!]
|
||||||
|
DEFAULT_ROLES: [String!]
|
||||||
|
JWT_ROLE_CLAIM: String
|
||||||
|
GOOGLE_CLIENT_ID: String
|
||||||
|
GOOGLE_CLIENT_SECRET: String
|
||||||
|
GITHUB_CLIENT_ID: String
|
||||||
|
GITHUB_CLIENT_SECRET: String
|
||||||
|
FACEBOOK_CLIENT_ID: String
|
||||||
|
FACEBOOK_CLIENT_SECRET: String
|
||||||
|
ORGANIZATION_NAME: String
|
||||||
|
ORGANIZATION_LOGO: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateConfigInput {
|
||||||
|
ADMIN_SECRET: String
|
||||||
|
DATABASE_TYPE: String
|
||||||
|
DATABASE_URL: String
|
||||||
|
DATABASE_NAME: String
|
||||||
|
SMTP_HOST: String
|
||||||
|
SMTP_PORT: String
|
||||||
|
SENDER_EMAIL: String
|
||||||
|
SENDER_PASSWORD: String
|
||||||
|
JWT_TYPE: String
|
||||||
|
JWT_SECRET: String
|
||||||
|
ALLOWED_ORIGINS: [String!]
|
||||||
|
AUTHORIZER_URL: String
|
||||||
|
APP_URL: String
|
||||||
|
REDIS_URL: String
|
||||||
|
COOKIE_NAME: String
|
||||||
|
RESET_PASSWORD_URL: String
|
||||||
|
DISABLE_EMAIL_VERIFICATION: Boolean
|
||||||
|
DISABLE_BASIC_AUTHENTICATION: Boolean
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN: Boolean
|
||||||
|
DISABLE_LOGIN_PAGE: Boolean
|
||||||
|
ROLES: [String!]
|
||||||
|
PROTECTED_ROLES: [String!]
|
||||||
|
DEFAULT_ROLES: [String!]
|
||||||
|
JWT_ROLE_CLAIM: String
|
||||||
|
GOOGLE_CLIENT_ID: String
|
||||||
|
GOOGLE_CLIENT_SECRET: String
|
||||||
|
GITHUB_CLIENT_ID: String
|
||||||
|
GITHUB_CLIENT_SECRET: String
|
||||||
|
FACEBOOK_CLIENT_ID: String
|
||||||
|
FACEBOOK_CLIENT_SECRET: String
|
||||||
|
ORGANIZATION_NAME: String
|
||||||
|
ORGANIZATION_LOGO: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input AdminLoginInput {
|
||||||
|
admin_secret: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input AdminSignupInput {
|
||||||
|
admin_secret: String!
|
||||||
|
}
|
||||||
|
|
||||||
input SignUpInput {
|
input SignUpInput {
|
||||||
email: String!
|
email: String!
|
||||||
given_name: String
|
given_name: String
|
||||||
|
@ -153,13 +232,19 @@ type Mutation {
|
||||||
# admin only apis
|
# admin only apis
|
||||||
_delete_user(params: DeleteUserInput!): Response!
|
_delete_user(params: DeleteUserInput!): Response!
|
||||||
_update_user(params: UpdateUserInput!): User!
|
_update_user(params: UpdateUserInput!): User!
|
||||||
|
_admin_signup(params: AdminSignupInput!): Response!
|
||||||
|
_admin_login(params: AdminLoginInput!): Response!
|
||||||
|
_admin_logout: Response!
|
||||||
|
_update_config(params: UpdateConfigInput!): Response!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
meta: Meta!
|
meta: Meta!
|
||||||
session(roles: [String!]): AuthResponse
|
session(roles: [String!]): AuthResponse!
|
||||||
profile: User!
|
profile: User!
|
||||||
# admin only apis
|
# admin only apis
|
||||||
_users: [User!]!
|
_users: [User!]!
|
||||||
_verification_requests: [VerificationRequest!]!
|
_verification_requests: [VerificationRequest!]!
|
||||||
|
_admin_session: Response!
|
||||||
|
_config: Config!
|
||||||
}
|
}
|
||||||
|
|
|
@ -55,6 +55,22 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, params model.UpdateUs
|
||||||
return resolvers.UpdateUser(ctx, params)
|
return resolvers.UpdateUser(ctx, params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) {
|
||||||
|
return resolvers.AdminSignupResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) AdminLogin(ctx context.Context, params model.AdminLoginInput) (*model.Response, error) {
|
||||||
|
return resolvers.AdminLoginResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) AdminLogout(ctx context.Context) (*model.Response, error) {
|
||||||
|
return resolvers.AdminLogout(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *mutationResolver) UpdateConfig(ctx context.Context, params model.UpdateConfigInput) (*model.Response, error) {
|
||||||
|
return resolvers.UpdateConfigResolver(ctx, params)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
|
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
|
||||||
return resolvers.Meta(ctx)
|
return resolvers.Meta(ctx)
|
||||||
}
|
}
|
||||||
|
@ -75,13 +91,19 @@ func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.Veri
|
||||||
return resolvers.VerificationRequests(ctx)
|
return resolvers.VerificationRequests(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) AdminSession(ctx context.Context) (*model.Response, error) {
|
||||||
|
return resolvers.AdminSession(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *queryResolver) Config(ctx context.Context) (*model.Config, error) {
|
||||||
|
return resolvers.ConfigResolver(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
// Mutation returns generated.MutationResolver implementation.
|
// Mutation returns generated.MutationResolver implementation.
|
||||||
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
|
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
|
||||||
|
|
||||||
// Query returns generated.QueryResolver implementation.
|
// Query returns generated.QueryResolver implementation.
|
||||||
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
||||||
|
|
||||||
type (
|
type mutationResolver struct{ *Resolver }
|
||||||
mutationResolver struct{ *Resolver }
|
type queryResolver struct{ *Resolver }
|
||||||
queryResolver struct{ *Resolver }
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/base64"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -30,17 +29,17 @@ func AppHandler() gin.HandlerFunc {
|
||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
stateObj.AuthorizerURL = constants.AUTHORIZER_URL
|
stateObj.AuthorizerURL = constants.EnvData.AUTHORIZER_URL
|
||||||
stateObj.RedirectURL = constants.AUTHORIZER_URL + "/app"
|
stateObj.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/app"
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
decodedState, err := base64.StdEncoding.DecodeString(state)
|
decodedState, err := utils.DecryptB64(state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
|
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = json.Unmarshal(decodedState, &stateObj)
|
err = json.Unmarshal([]byte(decodedState), &stateObj)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
|
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
|
||||||
return
|
return
|
||||||
|
@ -60,7 +59,7 @@ func AppHandler() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
|
|
||||||
// validate host and domain of authorizer url
|
// validate host and domain of authorizer url
|
||||||
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.AUTHORIZER_URL {
|
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.EnvData.AUTHORIZER_URL {
|
||||||
c.JSON(400, gin.H{"error": "invalid host url"})
|
c.JSON(400, gin.H{"error": "invalid host url"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -77,8 +76,8 @@ func AppHandler() gin.HandlerFunc {
|
||||||
"data": map[string]string{
|
"data": map[string]string{
|
||||||
"authorizerURL": stateObj.AuthorizerURL,
|
"authorizerURL": stateObj.AuthorizerURL,
|
||||||
"redirectURL": stateObj.RedirectURL,
|
"redirectURL": stateObj.RedirectURL,
|
||||||
"organizationName": constants.ORGANIZATION_NAME,
|
"organizationName": constants.EnvData.ORGANIZATION_NAME,
|
||||||
"organizationLogo": constants.ORGANIZATION_LOGO,
|
"organizationLogo": constants.EnvData.ORGANIZATION_LOGO,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
24
server/handlers/dashboard.go
Normal file
24
server/handlers/dashboard.go
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func DashboardHandler() gin.HandlerFunc {
|
||||||
|
return func(c *gin.Context) {
|
||||||
|
isOnboardingCompleted := false
|
||||||
|
|
||||||
|
if constants.EnvData.ADMIN_SECRET != "" {
|
||||||
|
isOnboardingCompleted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
c.HTML(http.StatusOK, "dashboard.tmpl", gin.H{
|
||||||
|
"data": map[string]interface{}{
|
||||||
|
"isOnboardingCompleted": isOnboardingCompleted,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -195,7 +195,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||||
// make sure inputRoles don't include protected roles
|
// make sure inputRoles don't include protected roles
|
||||||
hasProtectedRole := false
|
hasProtectedRole := false
|
||||||
for _, ir := range inputRoles {
|
for _, ir := range inputRoles {
|
||||||
if utils.StringSliceContains(constants.PROTECTED_ROLES, ir) {
|
if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ir) {
|
||||||
hasProtectedRole = true
|
hasProtectedRole = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,7 +238,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||||
// check if it contains protected unassigned role
|
// check if it contains protected unassigned role
|
||||||
hasProtectedRole := false
|
hasProtectedRole := false
|
||||||
for _, ur := range unasignedRoles {
|
for _, ur := range unasignedRoles {
|
||||||
if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) {
|
if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) {
|
||||||
hasProtectedRole = true
|
hasProtectedRole = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,14 +34,14 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
||||||
|
|
||||||
// use protected roles verification for admin login only.
|
// use protected roles verification for admin login only.
|
||||||
// though if not associated with user, it will be rejected from oauth_callback
|
// though if not associated with user, it will be rejected from oauth_callback
|
||||||
if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), rolesSplit) {
|
if !utils.IsValidRoles(append([]string{}, append(constants.EnvData.ROLES, constants.EnvData.PROTECTED_ROLES...)...), rolesSplit) {
|
||||||
c.JSON(400, gin.H{
|
c.JSON(400, gin.H{
|
||||||
"error": "invalid role",
|
"error": "invalid role",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
roles = strings.Join(constants.DEFAULT_ROLES, ",")
|
roles = strings.Join(constants.EnvData.DEFAULT_ROLES, ",")
|
||||||
}
|
}
|
||||||
|
|
||||||
uuid := uuid.New()
|
uuid := uuid.New()
|
||||||
|
@ -57,7 +57,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
||||||
}
|
}
|
||||||
session.SetSocailLoginState(oauthStateString, enum.Google.String())
|
session.SetSocailLoginState(oauthStateString, enum.Google.String())
|
||||||
// during the init of OAuthProvider authorizer url might be empty
|
// during the init of OAuthProvider authorizer url might be empty
|
||||||
oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google"
|
oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google"
|
||||||
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
case enum.Github.String():
|
case enum.Github.String():
|
||||||
|
@ -66,7 +66,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
session.SetSocailLoginState(oauthStateString, enum.Github.String())
|
session.SetSocailLoginState(oauthStateString, enum.Github.String())
|
||||||
oauth.OAuthProviders.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github"
|
oauth.OAuthProviders.GithubConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github"
|
||||||
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
case enum.Facebook.String():
|
case enum.Facebook.String():
|
||||||
|
@ -75,7 +75,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
session.SetSocailLoginState(oauthStateString, enum.Facebook.String())
|
session.SetSocailLoginState(oauthStateString, enum.Facebook.String())
|
||||||
oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook"
|
oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/facebook"
|
||||||
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
|
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
|
||||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -22,20 +22,22 @@ func main() {
|
||||||
env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
|
env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
constants.VERSION = VERSION
|
constants.EnvData.VERSION = VERSION
|
||||||
|
|
||||||
env.InitEnv()
|
env.InitEnv()
|
||||||
db.InitDB()
|
db.InitDB()
|
||||||
|
env.PersistEnv()
|
||||||
|
|
||||||
session.InitSession()
|
session.InitSession()
|
||||||
oauth.InitOAuth()
|
oauth.InitOAuth()
|
||||||
utils.InitServer()
|
utils.InitServer()
|
||||||
|
|
||||||
router := router.InitRouter()
|
router := router.InitRouter()
|
||||||
|
|
||||||
|
router.LoadHTMLGlob("templates/*")
|
||||||
// login page app related routes.
|
// login page app related routes.
|
||||||
// if we put them in router file then tests would fail as templates or build path will be different
|
// if we put them in router file then tests would fail as templates or build path will be different
|
||||||
if !constants.DISABLE_LOGIN_PAGE {
|
if !constants.EnvData.DISABLE_LOGIN_PAGE {
|
||||||
router.LoadHTMLGlob("templates/*")
|
|
||||||
app := router.Group("/app")
|
app := router.Group("/app")
|
||||||
{
|
{
|
||||||
app.Static("/build", "app/build")
|
app.Static("/build", "app/build")
|
||||||
|
@ -44,5 +46,11 @@ func main() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
router.Run(":" + constants.PORT)
|
app := router.Group("/dashboard")
|
||||||
|
{
|
||||||
|
app.Static("/build", "dashboard/build")
|
||||||
|
app.GET("/", handlers.DashboardHandler())
|
||||||
|
}
|
||||||
|
|
||||||
|
router.Run(":" + constants.EnvData.PORT)
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,10 +11,10 @@ import (
|
||||||
|
|
||||||
func GinContextToContextMiddleware() gin.HandlerFunc {
|
func GinContextToContextMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
if constants.AUTHORIZER_URL == "" {
|
if constants.EnvData.AUTHORIZER_URL == "" {
|
||||||
url := location.Get(c)
|
url := location.Get(c)
|
||||||
constants.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host
|
constants.EnvData.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host
|
||||||
log.Println("authorizer url:", constants.AUTHORIZER_URL)
|
log.Println("authorizer url:", constants.EnvData.AUTHORIZER_URL)
|
||||||
}
|
}
|
||||||
ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
|
ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
|
||||||
c.Request = c.Request.WithContext(ctx)
|
c.Request = c.Request.WithContext(ctx)
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package middlewares
|
package middlewares
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
@ -9,7 +8,6 @@ import (
|
||||||
func CORSMiddleware() gin.HandlerFunc {
|
func CORSMiddleware() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
origin := c.Request.Header.Get("Origin")
|
origin := c.Request.Header.Get("Origin")
|
||||||
constants.APP_URL = origin
|
|
||||||
|
|
||||||
if utils.IsValidOrigin(origin) {
|
if utils.IsValidOrigin(origin) {
|
||||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||||
|
|
|
@ -28,33 +28,33 @@ var (
|
||||||
|
|
||||||
func InitOAuth() {
|
func InitOAuth() {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
if constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "" {
|
if constants.EnvData.GOOGLE_CLIENT_ID != "" && constants.EnvData.GOOGLE_CLIENT_SECRET != "" {
|
||||||
p, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
p, err := oidc.NewProvider(ctx, "https://accounts.google.com")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("error creating oidc provider for google:", err)
|
log.Fatalln("error creating oidc provider for google:", err)
|
||||||
}
|
}
|
||||||
OIDCProviders.GoogleOIDC = p
|
OIDCProviders.GoogleOIDC = p
|
||||||
OAuthProviders.GoogleConfig = &oauth2.Config{
|
OAuthProviders.GoogleConfig = &oauth2.Config{
|
||||||
ClientID: constants.GOOGLE_CLIENT_ID,
|
ClientID: constants.EnvData.GOOGLE_CLIENT_ID,
|
||||||
ClientSecret: constants.GOOGLE_CLIENT_SECRET,
|
ClientSecret: constants.EnvData.GOOGLE_CLIENT_SECRET,
|
||||||
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/google",
|
RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google",
|
||||||
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
|
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
|
||||||
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "" {
|
if constants.EnvData.GITHUB_CLIENT_ID != "" && constants.EnvData.GITHUB_CLIENT_SECRET != "" {
|
||||||
OAuthProviders.GithubConfig = &oauth2.Config{
|
OAuthProviders.GithubConfig = &oauth2.Config{
|
||||||
ClientID: constants.GITHUB_CLIENT_ID,
|
ClientID: constants.EnvData.GITHUB_CLIENT_ID,
|
||||||
ClientSecret: constants.GITHUB_CLIENT_SECRET,
|
ClientSecret: constants.EnvData.GITHUB_CLIENT_SECRET,
|
||||||
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/github",
|
RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github",
|
||||||
Endpoint: githubOAuth2.Endpoint,
|
Endpoint: githubOAuth2.Endpoint,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "" {
|
if constants.EnvData.FACEBOOK_CLIENT_ID != "" && constants.EnvData.FACEBOOK_CLIENT_SECRET != "" {
|
||||||
OAuthProviders.FacebookConfig = &oauth2.Config{
|
OAuthProviders.FacebookConfig = &oauth2.Config{
|
||||||
ClientID: constants.FACEBOOK_CLIENT_ID,
|
ClientID: constants.EnvData.FACEBOOK_CLIENT_ID,
|
||||||
ClientSecret: constants.FACEBOOK_CLIENT_SECRET,
|
ClientSecret: constants.EnvData.FACEBOOK_CLIENT_SECRET,
|
||||||
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/facebook",
|
RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/facebook",
|
||||||
Endpoint: facebookOAuth2.Endpoint,
|
Endpoint: facebookOAuth2.Endpoint,
|
||||||
Scopes: []string{"public_profile", "email"},
|
Scopes: []string{"public_profile", "email"},
|
||||||
}
|
}
|
||||||
|
|
34
server/resolvers/admin_login.go
Normal file
34
server/resolvers/admin_login.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Response
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if params.AdminSecret != constants.EnvData.ADMIN_SECRET {
|
||||||
|
return res, fmt.Errorf(`invalid admin secret`)
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
utils.SetAdminCookie(gc, hashedKey)
|
||||||
|
|
||||||
|
res = &model.Response{
|
||||||
|
Message: "admin logged in successfully",
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
29
server/resolvers/admin_logout.go
Normal file
29
server/resolvers/admin_logout.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminLogout(ctx context.Context) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Response
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.IsSuperAdmin(gc) {
|
||||||
|
return res, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.DeleteAdminCookie(gc)
|
||||||
|
|
||||||
|
res = &model.Response{
|
||||||
|
Message: "admin logged out successfully",
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
34
server/resolvers/admin_session.go
Normal file
34
server/resolvers/admin_session.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminSession(ctx context.Context) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Response
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.IsSuperAdmin(gc) {
|
||||||
|
return res, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
utils.SetAdminCookie(gc, hashedKey)
|
||||||
|
|
||||||
|
res = &model.Response{
|
||||||
|
Message: "admin logged in successfully",
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
77
server/resolvers/admin_signup.go
Normal file
77
server/resolvers/admin_signup.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AdminSignupResolver(ctx context.Context, params model.AdminSignupInput) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Response
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(params.AdminSecret) == "" {
|
||||||
|
err = fmt.Errorf("please select secure admin secret")
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(params.AdminSecret) < 6 {
|
||||||
|
err = fmt.Errorf("admin secret must be at least 6 characters")
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if constants.EnvData.ADMIN_SECRET != "" {
|
||||||
|
err = fmt.Errorf("admin sign up already completed")
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
constants.EnvData.ADMIN_SECRET = params.AdminSecret
|
||||||
|
|
||||||
|
// consvert EnvData to JSON
|
||||||
|
var jsonData map[string]interface{}
|
||||||
|
|
||||||
|
jsonBytes, err := json.Marshal(constants.EnvData)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(jsonBytes, &jsonData); err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := db.Mgr.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := utils.EncryptConfig(jsonData)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Config = configData
|
||||||
|
if _, err := db.Mgr.UpdateConfig(config); err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedKey, err := utils.HashPassword(params.AdminSecret)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
utils.SetAdminCookie(gc, hashedKey)
|
||||||
|
|
||||||
|
res = &model.Response{
|
||||||
|
Message: "admin signed up successfully",
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
60
server/resolvers/config.go
Normal file
60
server/resolvers/config.go
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ConfigResolver(ctx context.Context) (*model.Config, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Config
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.IsSuperAdmin(gc) {
|
||||||
|
return res, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
res = &model.Config{
|
||||||
|
AdminSecret: &constants.EnvData.ADMIN_SECRET,
|
||||||
|
DatabaseType: &constants.EnvData.DATABASE_TYPE,
|
||||||
|
DatabaseURL: &constants.EnvData.DATABASE_URL,
|
||||||
|
DatabaseName: &constants.EnvData.DATABASE_NAME,
|
||||||
|
SMTPHost: &constants.EnvData.SMTP_HOST,
|
||||||
|
SMTPPort: &constants.EnvData.SMTP_PORT,
|
||||||
|
SMTPPassword: &constants.EnvData.SMTP_PASSWORD,
|
||||||
|
SMTPUsername: &constants.EnvData.SMTP_USERNAME,
|
||||||
|
SenderEmail: &constants.EnvData.SENDER_EMAIL,
|
||||||
|
JwtType: &constants.EnvData.JWT_TYPE,
|
||||||
|
JwtSecret: &constants.EnvData.JWT_SECRET,
|
||||||
|
AllowedOrigins: constants.EnvData.ALLOWED_ORIGINS,
|
||||||
|
AuthorizerURL: &constants.EnvData.AUTHORIZER_URL,
|
||||||
|
AppURL: &constants.EnvData.APP_URL,
|
||||||
|
RedisURL: &constants.EnvData.REDIS_URL,
|
||||||
|
CookieName: &constants.EnvData.COOKIE_NAME,
|
||||||
|
ResetPasswordURL: &constants.EnvData.RESET_PASSWORD_URL,
|
||||||
|
DisableEmailVerification: &constants.EnvData.DISABLE_EMAIL_VERIFICATION,
|
||||||
|
DisableBasicAuthentication: &constants.EnvData.DISABLE_BASIC_AUTHENTICATION,
|
||||||
|
DisableMagicLinkLogin: &constants.EnvData.DISABLE_MAGIC_LINK_LOGIN,
|
||||||
|
DisableLoginPage: &constants.EnvData.DISABLE_LOGIN_PAGE,
|
||||||
|
Roles: constants.EnvData.ROLES,
|
||||||
|
ProtectedRoles: constants.EnvData.PROTECTED_ROLES,
|
||||||
|
DefaultRoles: constants.EnvData.DEFAULT_ROLES,
|
||||||
|
JwtRoleClaim: &constants.EnvData.JWT_ROLE_CLAIM,
|
||||||
|
GoogleClientID: &constants.EnvData.GOOGLE_CLIENT_ID,
|
||||||
|
GoogleClientSecret: &constants.EnvData.GOOGLE_CLIENT_SECRET,
|
||||||
|
GithubClientID: &constants.EnvData.GITHUB_CLIENT_ID,
|
||||||
|
GithubClientSecret: &constants.EnvData.GITHUB_CLIENT_SECRET,
|
||||||
|
FacebookClientID: &constants.EnvData.FACEBOOK_CLIENT_ID,
|
||||||
|
FacebookClientSecret: &constants.EnvData.FACEBOOK_CLIENT_SECRET,
|
||||||
|
OrganizationName: &constants.EnvData.ORGANIZATION_NAME,
|
||||||
|
OrganizationLogo: &constants.EnvData.ORGANIZATION_LOGO,
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
|
@ -20,7 +20,7 @@ func ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*mod
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
if constants.DISABLE_BASIC_AUTHENTICATION {
|
if constants.EnvData.DISABLE_BASIC_AUTHENTICATION {
|
||||||
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
host := gc.Request.Host
|
host := gc.Request.Host
|
||||||
|
|
|
@ -22,7 +22,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.DISABLE_BASIC_AUTHENTICATION {
|
if constants.EnvData.DISABLE_BASIC_AUTHENTICATION {
|
||||||
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
|
||||||
log.Println("compare password error:", err)
|
log.Println("compare password error:", err)
|
||||||
return res, fmt.Errorf(`invalid password`)
|
return res, fmt.Errorf(`invalid password`)
|
||||||
}
|
}
|
||||||
roles := constants.DEFAULT_ROLES
|
roles := constants.EnvData.DEFAULT_ROLES
|
||||||
currentRoles := strings.Split(user.Roles, ",")
|
currentRoles := strings.Split(user.Roles, ",")
|
||||||
if len(params.Roles) > 0 {
|
if len(params.Roles) > 0 {
|
||||||
if !utils.IsValidRoles(currentRoles, params.Roles) {
|
if !utils.IsValidRoles(currentRoles, params.Roles) {
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
|
func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
|
||||||
var res *model.Response
|
var res *model.Response
|
||||||
|
|
||||||
if constants.DISABLE_MAGIC_LINK_LOGIN {
|
if constants.EnvData.DISABLE_MAGIC_LINK_LOGIN {
|
||||||
return res, fmt.Errorf(`magic link login is disabled for this instance`)
|
return res, fmt.Errorf(`magic link login is disabled for this instance`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,13 +41,13 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod
|
||||||
// define roles for new user
|
// define roles for new user
|
||||||
if len(params.Roles) > 0 {
|
if len(params.Roles) > 0 {
|
||||||
// check if roles exists
|
// check if roles exists
|
||||||
if !utils.IsValidRoles(constants.ROLES, params.Roles) {
|
if !utils.IsValidRoles(constants.EnvData.ROLES, params.Roles) {
|
||||||
return res, fmt.Errorf(`invalid roles`)
|
return res, fmt.Errorf(`invalid roles`)
|
||||||
} else {
|
} else {
|
||||||
inputRoles = params.Roles
|
inputRoles = params.Roles
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inputRoles = constants.DEFAULT_ROLES
|
inputRoles = constants.EnvData.DEFAULT_ROLES
|
||||||
}
|
}
|
||||||
|
|
||||||
user.Roles = strings.Join(inputRoles, ",")
|
user.Roles = strings.Join(inputRoles, ",")
|
||||||
|
@ -72,7 +72,7 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod
|
||||||
// check if it contains protected unassigned role
|
// check if it contains protected unassigned role
|
||||||
hasProtectedRole := false
|
hasProtectedRole := false
|
||||||
for _, ur := range unasignedRoles {
|
for _, ur := range unasignedRoles {
|
||||||
if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) {
|
if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) {
|
||||||
hasProtectedRole = true
|
hasProtectedRole = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !constants.DISABLE_EMAIL_VERIFICATION {
|
if !constants.EnvData.DISABLE_EMAIL_VERIFICATION {
|
||||||
// insert verification request
|
// insert verification request
|
||||||
verificationType := enum.MagicLinkLogin.String()
|
verificationType := enum.MagicLinkLogin.String()
|
||||||
token, err := utils.CreateVerificationToken(params.Email, verificationType)
|
token, err := utils.CreateVerificationToken(params.Email, verificationType)
|
||||||
|
|
|
@ -15,7 +15,7 @@ import (
|
||||||
|
|
||||||
func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
|
func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
|
||||||
var res *model.Response
|
var res *model.Response
|
||||||
if constants.DISABLE_BASIC_AUTHENTICATION {
|
if constants.EnvData.DISABLE_BASIC_AUTHENTICATION {
|
||||||
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
|
||||||
expiresTimeObj := time.Unix(expiresAt, 0)
|
expiresTimeObj := time.Unix(expiresAt, 0)
|
||||||
currentTimeObj := time.Now()
|
currentTimeObj := time.Now()
|
||||||
|
|
||||||
claimRoleInterface := claim[constants.JWT_ROLE_CLAIM].([]interface{})
|
claimRoleInterface := claim[constants.EnvData.JWT_ROLE_CLAIM].([]interface{})
|
||||||
claimRoles := make([]string, len(claimRoleInterface))
|
claimRoles := make([]string, len(claimRoleInterface))
|
||||||
for i, v := range claimRoleInterface {
|
for i, v := range claimRoleInterface {
|
||||||
claimRoles[i] = v.(string)
|
claimRoles[i] = v.(string)
|
||||||
|
|
|
@ -22,7 +22,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if constants.DISABLE_BASIC_AUTHENTICATION {
|
if constants.EnvData.DISABLE_BASIC_AUTHENTICATION {
|
||||||
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
|
||||||
}
|
}
|
||||||
if params.ConfirmPassword != params.Password {
|
if params.ConfirmPassword != params.Password {
|
||||||
|
@ -52,13 +52,13 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
|
||||||
|
|
||||||
if len(params.Roles) > 0 {
|
if len(params.Roles) > 0 {
|
||||||
// check if roles exists
|
// check if roles exists
|
||||||
if !utils.IsValidRoles(constants.ROLES, params.Roles) {
|
if !utils.IsValidRoles(constants.EnvData.ROLES, params.Roles) {
|
||||||
return res, fmt.Errorf(`invalid roles`)
|
return res, fmt.Errorf(`invalid roles`)
|
||||||
} else {
|
} else {
|
||||||
inputRoles = params.Roles
|
inputRoles = params.Roles
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
inputRoles = constants.DEFAULT_ROLES
|
inputRoles = constants.EnvData.DEFAULT_ROLES
|
||||||
}
|
}
|
||||||
|
|
||||||
user := db.User{
|
user := db.User{
|
||||||
|
@ -103,7 +103,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
|
||||||
}
|
}
|
||||||
|
|
||||||
user.SignupMethods = enum.BasicAuth.String()
|
user.SignupMethods = enum.BasicAuth.String()
|
||||||
if constants.DISABLE_EMAIL_VERIFICATION {
|
if constants.EnvData.DISABLE_EMAIL_VERIFICATION {
|
||||||
now := time.Now().Unix()
|
now := time.Now().Unix()
|
||||||
user.EmailVerifiedAt = &now
|
user.EmailVerifiedAt = &now
|
||||||
}
|
}
|
||||||
|
@ -115,7 +115,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
|
||||||
roles := strings.Split(user.Roles, ",")
|
roles := strings.Split(user.Roles, ",")
|
||||||
userToReturn := utils.GetResponseUserData(user)
|
userToReturn := utils.GetResponseUserData(user)
|
||||||
|
|
||||||
if !constants.DISABLE_EMAIL_VERIFICATION {
|
if !constants.EnvData.DISABLE_EMAIL_VERIFICATION {
|
||||||
// insert verification request
|
// insert verification request
|
||||||
verificationType := enum.BasicAuthSignup.String()
|
verificationType := enum.BasicAuthSignup.String()
|
||||||
token, err := utils.CreateVerificationToken(params.Email, verificationType)
|
token, err := utils.CreateVerificationToken(params.Email, verificationType)
|
||||||
|
|
105
server/resolvers/update_config.go
Normal file
105
server/resolvers/update_config.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package resolvers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
|
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
func UpdateConfigResolver(ctx context.Context, params model.UpdateConfigInput) (*model.Response, error) {
|
||||||
|
gc, err := utils.GinContextFromContext(ctx)
|
||||||
|
var res *model.Response
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !utils.IsSuperAdmin(gc) {
|
||||||
|
return res, fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
var data map[string]interface{}
|
||||||
|
byteData, err := json.Marshal(params)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("error marshalling params: %t", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(byteData, &data)
|
||||||
|
if err != nil {
|
||||||
|
return res, fmt.Errorf("error un-marshalling params: %t", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedData := make(map[string]interface{})
|
||||||
|
for key, value := range data {
|
||||||
|
if value != nil {
|
||||||
|
fieldType := reflect.TypeOf(value).String()
|
||||||
|
|
||||||
|
if fieldType == "string" || fieldType == "bool" {
|
||||||
|
updatedData[key] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
if fieldType == "[]interface {}" {
|
||||||
|
stringArr := []string{}
|
||||||
|
for _, v := range value.([]interface{}) {
|
||||||
|
stringArr = append(stringArr, v.(string))
|
||||||
|
}
|
||||||
|
updatedData[key] = stringArr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// handle derivative cases like disabling email verification & magic login
|
||||||
|
// in case SMTP is off but env is set to true
|
||||||
|
if updatedData["SMTP_HOST"] == "" || updatedData["SENDER_EMAIL"] == "" || updatedData["SENDER_PASSWORD"] == "" {
|
||||||
|
if !updatedData["DISABLE_EMAIL_VERIFICATION"].(bool) {
|
||||||
|
updatedData["DISABLE_EMAIL_VERIFICATION"] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updatedData["DISABLE_MAGIC_LINK_LOGIN"].(bool) {
|
||||||
|
updatedData["DISABLE_MAGIC_LINK_LOGIN"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := db.Mgr.GetConfig()
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedConfig, err := utils.EncryptConfig(updatedData)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case of db change re-initialize db
|
||||||
|
if params.DatabaseType != nil || params.DatabaseURL != nil || params.DatabaseName != nil {
|
||||||
|
db.InitDB()
|
||||||
|
}
|
||||||
|
|
||||||
|
// in case of admin secret change update the cookie with new hash
|
||||||
|
if params.AdminSecret != nil {
|
||||||
|
hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
utils.SetAdminCookie(gc, hashedKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
config.Config = encryptedConfig
|
||||||
|
_, err = db.Mgr.UpdateConfig(config)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error updating config:", err)
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res = &model.Response{
|
||||||
|
Message: "configurations updated successfully",
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
|
@ -112,7 +112,7 @@ func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User,
|
||||||
inputRoles = append(inputRoles, *item)
|
inputRoles = append(inputRoles, *item)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), inputRoles) {
|
if !utils.IsValidRoles(append([]string{}, append(constants.EnvData.ROLES, constants.EnvData.PROTECTED_ROLES...)...), inputRoles) {
|
||||||
return res, fmt.Errorf("invalid list of roles")
|
return res, fmt.Errorf("invalid list of roles")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -95,9 +95,9 @@ func RemoveSocialLoginState(key string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitSession() {
|
func InitSession() {
|
||||||
if constants.REDIS_URL != "" {
|
if constants.EnvData.REDIS_URL != "" {
|
||||||
log.Println("using redis store to save sessions")
|
log.Println("using redis store to save sessions")
|
||||||
opt, err := redis.ParseURL(constants.REDIS_URL)
|
opt, err := redis.ParseURL(constants.EnvData.REDIS_URL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalln("Error parsing redis url:", err)
|
log.Fatalln("Error parsing redis url:", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -14,10 +15,11 @@ import (
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/golang-jwt/jwt"
|
"github.com/golang-jwt/jwt"
|
||||||
"github.com/robertkrimen/otto"
|
"github.com/robertkrimen/otto"
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) {
|
func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) {
|
||||||
t := jwt.New(jwt.GetSigningMethod(constants.JWT_TYPE))
|
t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE))
|
||||||
expiryBound := time.Hour
|
expiryBound := time.Hour
|
||||||
if tokenType == enum.RefreshToken {
|
if tokenType == enum.RefreshToken {
|
||||||
// expires in 1 year
|
// expires in 1 year
|
||||||
|
@ -32,11 +34,11 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
|
||||||
json.Unmarshal(userBytes, &userMap)
|
json.Unmarshal(userBytes, &userMap)
|
||||||
|
|
||||||
customClaims := jwt.MapClaims{
|
customClaims := jwt.MapClaims{
|
||||||
"exp": expiresAt,
|
"exp": expiresAt,
|
||||||
"iat": time.Now().Unix(),
|
"iat": time.Now().Unix(),
|
||||||
"token_type": tokenType.String(),
|
"token_type": tokenType.String(),
|
||||||
"allowed_roles": strings.Split(user.Roles, ","),
|
"allowed_roles": strings.Split(user.Roles, ","),
|
||||||
constants.JWT_ROLE_CLAIM: roles,
|
constants.EnvData.JWT_ROLE_CLAIM: roles,
|
||||||
}
|
}
|
||||||
|
|
||||||
for k, v := range userMap {
|
for k, v := range userMap {
|
||||||
|
@ -77,7 +79,7 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
|
||||||
|
|
||||||
t.Claims = customClaims
|
t.Claims = customClaims
|
||||||
|
|
||||||
token, err := t.SignedString([]byte(constants.JWT_SECRET))
|
token, err := t.SignedString([]byte(constants.EnvData.JWT_SECRET))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", 0, err
|
return "", 0, err
|
||||||
}
|
}
|
||||||
|
@ -89,7 +91,6 @@ func GetAuthToken(gc *gin.Context) (string, error) {
|
||||||
token, err := GetCookie(gc)
|
token, err := GetCookie(gc)
|
||||||
if err != nil || token == "" {
|
if err != nil || token == "" {
|
||||||
// try to check in auth header for cookie
|
// try to check in auth header for cookie
|
||||||
log.Println("cookie not found checking headers")
|
|
||||||
auth := gc.Request.Header.Get("Authorization")
|
auth := gc.Request.Header.Get("Authorization")
|
||||||
if auth == "" {
|
if auth == "" {
|
||||||
return "", fmt.Errorf(`unauthorized`)
|
return "", fmt.Errorf(`unauthorized`)
|
||||||
|
@ -105,7 +106,7 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
|
||||||
claims := jwt.MapClaims{}
|
claims := jwt.MapClaims{}
|
||||||
|
|
||||||
_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
|
_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
|
||||||
return []byte(constants.JWT_SECRET), nil
|
return []byte(constants.EnvData.JWT_SECRET), nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return res, err
|
return res, err
|
||||||
|
@ -124,3 +125,29 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CreateAdminAuthToken(tokenType enum.TokenType, c *gin.Context) (string, error) {
|
||||||
|
return HashPassword(constants.EnvData.ADMIN_SECRET)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAdminAuthToken(gc *gin.Context) (string, error) {
|
||||||
|
token, err := GetAdminCookie(gc)
|
||||||
|
if err != nil || token == "" {
|
||||||
|
return "", fmt.Errorf("unauthorized")
|
||||||
|
}
|
||||||
|
|
||||||
|
// cookie escapes special characters like $
|
||||||
|
// hence we need to unescape before comparing
|
||||||
|
decodedValue, err := url.QueryUnescape(token)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bcrypt.CompareHashAndPassword([]byte(decodedValue), []byte(constants.EnvData.ADMIN_SECRET))
|
||||||
|
log.Println("error comparing hash:", err)
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf(`unauthorized`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return token, nil
|
||||||
|
}
|
||||||
|
|
|
@ -10,21 +10,21 @@ import (
|
||||||
func SetCookie(gc *gin.Context, token string) {
|
func SetCookie(gc *gin.Context, token string) {
|
||||||
secure := true
|
secure := true
|
||||||
httpOnly := true
|
httpOnly := true
|
||||||
host, _ := GetHostParts(constants.AUTHORIZER_URL)
|
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
|
||||||
domain := GetDomainName(constants.AUTHORIZER_URL)
|
domain := GetDomainName(constants.EnvData.AUTHORIZER_URL)
|
||||||
if domain != "localhost" {
|
if domain != "localhost" {
|
||||||
domain = "." + domain
|
domain = "." + domain
|
||||||
}
|
}
|
||||||
|
|
||||||
gc.SetSameSite(http.SameSiteNoneMode)
|
gc.SetSameSite(http.SameSiteNoneMode)
|
||||||
gc.SetCookie(constants.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
|
gc.SetCookie(constants.EnvData.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
|
||||||
gc.SetCookie(constants.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly)
|
gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly)
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetCookie(gc *gin.Context) (string, error) {
|
func GetCookie(gc *gin.Context) (string, error) {
|
||||||
cookie, err := gc.Request.Cookie(constants.COOKIE_NAME)
|
cookie, err := gc.Request.Cookie(constants.EnvData.COOKIE_NAME)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cookie, err = gc.Request.Cookie(constants.COOKIE_NAME + "-client")
|
cookie, err = gc.Request.Cookie(constants.EnvData.COOKIE_NAME + "-client")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -37,13 +37,37 @@ func DeleteCookie(gc *gin.Context) {
|
||||||
secure := true
|
secure := true
|
||||||
httpOnly := true
|
httpOnly := true
|
||||||
|
|
||||||
host, _ := GetHostParts(constants.AUTHORIZER_URL)
|
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
|
||||||
domain := GetDomainName(constants.AUTHORIZER_URL)
|
domain := GetDomainName(constants.EnvData.AUTHORIZER_URL)
|
||||||
if domain != "localhost" {
|
if domain != "localhost" {
|
||||||
domain = "." + domain
|
domain = "." + domain
|
||||||
}
|
}
|
||||||
|
|
||||||
gc.SetSameSite(http.SameSiteNoneMode)
|
gc.SetSameSite(http.SameSiteNoneMode)
|
||||||
gc.SetCookie(constants.COOKIE_NAME, "", -1, "/", host, secure, httpOnly)
|
gc.SetCookie(constants.EnvData.COOKIE_NAME, "", -1, "/", host, secure, httpOnly)
|
||||||
gc.SetCookie(constants.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly)
|
gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetAdminCookie(gc *gin.Context, token string) {
|
||||||
|
secure := true
|
||||||
|
httpOnly := true
|
||||||
|
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
|
||||||
|
|
||||||
|
gc.SetCookie(constants.EnvData.ADMIN_COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAdminCookie(gc *gin.Context) (string, error) {
|
||||||
|
cookie, err := gc.Request.Cookie(constants.EnvData.ADMIN_COOKIE_NAME)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cookie.Value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteAdminCookie(gc *gin.Context) {
|
||||||
|
secure := true
|
||||||
|
httpOnly := true
|
||||||
|
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
|
||||||
|
|
||||||
|
gc.SetCookie(constants.EnvData.ADMIN_COOKIE_NAME, "", -1, "/", host, secure, httpOnly)
|
||||||
}
|
}
|
||||||
|
|
83
server/utils/crypto.go
Normal file
83
server/utils/crypto.go
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/base64"
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EncryptB64(text string) string {
|
||||||
|
return base64.StdEncoding.EncodeToString([]byte(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecryptB64(s string) (string, error) {
|
||||||
|
data, err := base64.StdEncoding.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func EncryptAES(text []byte) ([]byte, error) {
|
||||||
|
key := []byte(constants.EnvData.ENCRYPTION_KEY)
|
||||||
|
c, err := aes.NewCipher(key)
|
||||||
|
var res []byte
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// gcm or Galois/Counter Mode, is a mode of operation
|
||||||
|
// for symmetric key cryptographic block ciphers
|
||||||
|
// - https://en.wikipedia.org/wiki/Galois/Counter_Mode
|
||||||
|
gcm, err := cipher.NewGCM(c)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a new byte array the size of the nonce
|
||||||
|
// which must be passed to Seal
|
||||||
|
nonce := make([]byte, gcm.NonceSize())
|
||||||
|
// populates our nonce with a cryptographically secure
|
||||||
|
// random sequence
|
||||||
|
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we encrypt our text using the Seal function
|
||||||
|
// Seal encrypts and authenticates plaintext, authenticates the
|
||||||
|
// additional data and appends the result to dst, returning the updated
|
||||||
|
// slice. The nonce must be NonceSize() bytes long and unique for all
|
||||||
|
// time, for a given key.
|
||||||
|
return gcm.Seal(nonce, nonce, text, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DecryptAES(ciphertext []byte) ([]byte, error) {
|
||||||
|
key := []byte(constants.EnvData.ENCRYPTION_KEY)
|
||||||
|
c, err := aes.NewCipher(key)
|
||||||
|
var res []byte
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
gcm, err := cipher.NewGCM(c)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonceSize := gcm.NonceSize()
|
||||||
|
if len(ciphertext) < nonceSize {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
|
||||||
|
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return plaintext, nil
|
||||||
|
}
|
|
@ -101,9 +101,9 @@ func SendVerificationMail(toEmail, token string) error {
|
||||||
</html>
|
</html>
|
||||||
`
|
`
|
||||||
data := make(map[string]interface{}, 3)
|
data := make(map[string]interface{}, 3)
|
||||||
data["OrgLogo"] = constants.ORGANIZATION_LOGO
|
data["OrgLogo"] = constants.EnvData.ORGANIZATION_LOGO
|
||||||
data["OrgName"] = constants.ORGANIZATION_NAME
|
data["OrgName"] = constants.EnvData.ORGANIZATION_NAME
|
||||||
data["AuthUrl"] = constants.AUTHORIZER_URL + "/verify_email?token=" + token
|
data["AuthUrl"] = constants.EnvData.AUTHORIZER_URL + "/verify_email?token=" + token
|
||||||
message = AddEmailTemplate(message, data, "verify_email.tmpl")
|
message = AddEmailTemplate(message, data, "verify_email.tmpl")
|
||||||
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
||||||
|
|
||||||
|
@ -112,8 +112,8 @@ func SendVerificationMail(toEmail, token string) error {
|
||||||
|
|
||||||
// SendForgotPasswordMail to send verification email
|
// SendForgotPasswordMail to send verification email
|
||||||
func SendForgotPasswordMail(toEmail, token, host string) error {
|
func SendForgotPasswordMail(toEmail, token, host string) error {
|
||||||
if constants.RESET_PASSWORD_URL == "" {
|
if constants.EnvData.RESET_PASSWORD_URL == "" {
|
||||||
constants.RESET_PASSWORD_URL = constants.AUTHORIZER_URL + "/app/reset-password"
|
constants.EnvData.RESET_PASSWORD_URL = constants.EnvData.AUTHORIZER_URL + "/app/reset-password"
|
||||||
}
|
}
|
||||||
|
|
||||||
// The receiver needs to be in slice as the receive supports multiple receiver
|
// The receiver needs to be in slice as the receive supports multiple receiver
|
||||||
|
@ -207,9 +207,9 @@ func SendForgotPasswordMail(toEmail, token, host string) error {
|
||||||
`
|
`
|
||||||
|
|
||||||
data := make(map[string]interface{}, 3)
|
data := make(map[string]interface{}, 3)
|
||||||
data["OrgLogo"] = constants.ORGANIZATION_LOGO
|
data["OrgLogo"] = constants.EnvData.ORGANIZATION_LOGO
|
||||||
data["ToEmail"] = constants.ORGANIZATION_NAME
|
data["ToEmail"] = constants.EnvData.ORGANIZATION_NAME
|
||||||
data["AuthUrl"] = constants.RESET_PASSWORD_URL + "?token=" + token
|
data["AuthUrl"] = constants.EnvData.RESET_PASSWORD_URL + "?token=" + token
|
||||||
message = AddEmailTemplate(message, data, "reset_password_email.tmpl")
|
message = AddEmailTemplate(message, data, "reset_password_email.tmpl")
|
||||||
|
|
||||||
return email.SendMail(Receiver, Subject, message)
|
return email.SendMail(Receiver, Subject, message)
|
||||||
|
|
30
server/utils/encrypt_config.go
Normal file
30
server/utils/encrypt_config.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
func EncryptConfig(data map[string]interface{}) ([]byte, error) {
|
||||||
|
jsonBytes, err := json.Marshal(data)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = json.Unmarshal(jsonBytes, &constants.EnvData)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, err := json.Marshal(constants.EnvData)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
encryptedConfig, err := EncryptAES(configData)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return encryptedConfig, nil
|
||||||
|
}
|
|
@ -16,7 +16,7 @@ func IsValidEmail(email string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsValidOrigin(url string) bool {
|
func IsValidOrigin(url string) bool {
|
||||||
if len(constants.ALLOWED_ORIGINS) == 1 && constants.ALLOWED_ORIGINS[0] == "*" {
|
if len(constants.EnvData.ALLOWED_ORIGINS) == 1 && constants.EnvData.ALLOWED_ORIGINS[0] == "*" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,7 @@ func IsValidOrigin(url string) bool {
|
||||||
hostName, port := GetHostParts(url)
|
hostName, port := GetHostParts(url)
|
||||||
currentOrigin := hostName + ":" + port
|
currentOrigin := hostName + ":" + port
|
||||||
|
|
||||||
for _, origin := range constants.ALLOWED_ORIGINS {
|
for _, origin := range constants.EnvData.ALLOWED_ORIGINS {
|
||||||
replacedString := origin
|
replacedString := origin
|
||||||
// if has regex whitelisted domains
|
// if has regex whitelisted domains
|
||||||
if strings.Contains(origin, "*") {
|
if strings.Contains(origin, "*") {
|
||||||
|
@ -50,12 +50,17 @@ func IsValidOrigin(url string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsSuperAdmin(gc *gin.Context) bool {
|
func IsSuperAdmin(gc *gin.Context) bool {
|
||||||
secret := gc.Request.Header.Get("x-authorizer-admin-secret")
|
token, err := GetAdminAuthToken(gc)
|
||||||
if secret == "" {
|
if err != nil {
|
||||||
return false
|
secret := gc.Request.Header.Get("x-authorizer-admin-secret")
|
||||||
|
if secret == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return secret == constants.EnvData.ADMIN_SECRET
|
||||||
}
|
}
|
||||||
|
|
||||||
return secret == constants.ADMIN_SECRET
|
return token != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsValidRoles(userRoles []string, roles []string) bool {
|
func IsValidRoles(userRoles []string, roles []string) bool {
|
||||||
|
|
|
@ -20,23 +20,23 @@ type CustomClaim struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateVerificationToken(email string, tokenType string) (string, error) {
|
func CreateVerificationToken(email string, tokenType string) (string, error) {
|
||||||
t := jwt.New(jwt.GetSigningMethod(constants.JWT_TYPE))
|
t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE))
|
||||||
|
|
||||||
t.Claims = &CustomClaim{
|
t.Claims = &CustomClaim{
|
||||||
&jwt.StandardClaims{
|
&jwt.StandardClaims{
|
||||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||||
},
|
},
|
||||||
tokenType,
|
tokenType,
|
||||||
UserInfo{Email: email, Host: constants.AUTHORIZER_URL, RedirectURL: constants.APP_URL},
|
UserInfo{Email: email, Host: constants.EnvData.AUTHORIZER_URL, RedirectURL: constants.EnvData.APP_URL},
|
||||||
}
|
}
|
||||||
|
|
||||||
return t.SignedString([]byte(constants.JWT_SECRET))
|
return t.SignedString([]byte(constants.EnvData.JWT_SECRET))
|
||||||
}
|
}
|
||||||
|
|
||||||
func VerifyVerificationToken(token string) (*CustomClaim, error) {
|
func VerifyVerificationToken(token string) (*CustomClaim, error) {
|
||||||
claims := &CustomClaim{}
|
claims := &CustomClaim{}
|
||||||
_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
|
_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
|
||||||
return []byte(constants.JWT_SECRET), nil
|
return []byte(constants.EnvData.JWT_SECRET), nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return claims, err
|
return claims, err
|
||||||
|
|
16
templates/dashboard.tmpl
Normal file
16
templates/dashboard.tmpl
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Document</title>
|
||||||
|
<script>
|
||||||
|
window.__authorizer__ = {{.data}}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/dashboard/build/index.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue
Block a user