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 rm -f github-assets-uploader.tar.gz && \
|
||||
github-assets-uploader -version && \
|
||||
make build-app
|
||||
make build-app && \
|
||||
make build-dashboard
|
||||
- name: Print Go paths
|
||||
run: whereis go
|
||||
- name: Print Go Version
|
||||
|
@ -37,12 +38,12 @@ jobs:
|
|||
make clean && \
|
||||
CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
|
||||
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
|
||||
run: |
|
||||
make clean && \
|
||||
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
|
||||
run: |
|
||||
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
|
||||
app/node_modules
|
||||
app/build
|
||||
dashboard/node_modules
|
||||
dashboard/build
|
||||
build
|
||||
.env
|
||||
data.db
|
||||
|
|
|
@ -14,14 +14,17 @@ RUN apk add build-base &&\
|
|||
FROM node:17-alpine3.12 as node-builder
|
||||
WORKDIR /authorizer
|
||||
COPY app app
|
||||
COPY dashboard dashboard
|
||||
COPY Makefile .
|
||||
RUN apk add build-base &&\
|
||||
make build-app
|
||||
make build-app && \
|
||||
make build-dashboard
|
||||
|
||||
FROM alpine:latest
|
||||
WORKDIR /root/
|
||||
RUN mkdir app
|
||||
RUN mkdir app dashboard
|
||||
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 templates templates
|
||||
EXPOSE 8080
|
||||
|
|
2
Makefile
2
Makefile
|
@ -5,6 +5,8 @@ cmd:
|
|||
cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server'
|
||||
build-app:
|
||||
cd app && npm i && npm run build
|
||||
build-dashboard:
|
||||
cd dashboard && npm i && npm run build
|
||||
clean:
|
||||
rm -rf build
|
||||
test:
|
||||
|
|
|
@ -1,3 +1,14 @@
|
|||
# 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"
|
||||
make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make
|
||||
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"
|
||||
RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION})
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -24,7 +26,10 @@ func deleteUserTest(s TestSetup, t *testing.T) {
|
|||
})
|
||||
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{
|
||||
Email: email,
|
||||
})
|
||||
|
|
|
@ -8,18 +8,18 @@ import (
|
|||
)
|
||||
|
||||
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.ENV, "production")
|
||||
assert.False(t, constants.DISABLE_EMAIL_VERIFICATION)
|
||||
assert.False(t, constants.DISABLE_MAGIC_LINK_LOGIN)
|
||||
assert.False(t, constants.DISABLE_BASIC_AUTHENTICATION)
|
||||
assert.Equal(t, constants.JWT_TYPE, "HS256")
|
||||
assert.Equal(t, constants.JWT_SECRET, "random_string")
|
||||
assert.Equal(t, constants.JWT_ROLE_CLAIM, "role")
|
||||
assert.EqualValues(t, constants.ROLES, []string{"user"})
|
||||
assert.EqualValues(t, constants.DEFAULT_ROLES, []string{"user"})
|
||||
assert.EqualValues(t, constants.PROTECTED_ROLES, []string{"admin"})
|
||||
assert.EqualValues(t, constants.ALLOWED_ORIGINS, []string{"*"})
|
||||
assert.Equal(t, constants.EnvData.ADMIN_SECRET, "admin")
|
||||
assert.Equal(t, constants.EnvData.ENV, "production")
|
||||
assert.False(t, constants.EnvData.DISABLE_EMAIL_VERIFICATION)
|
||||
assert.False(t, constants.EnvData.DISABLE_MAGIC_LINK_LOGIN)
|
||||
assert.False(t, constants.EnvData.DISABLE_BASIC_AUTHENTICATION)
|
||||
assert.Equal(t, constants.EnvData.JWT_TYPE, "HS256")
|
||||
assert.Equal(t, constants.EnvData.JWT_SECRET, "random_string")
|
||||
assert.Equal(t, constants.EnvData.JWT_ROLE_CLAIM, "role")
|
||||
assert.EqualValues(t, constants.EnvData.ROLES, []string{"user"})
|
||||
assert.EqualValues(t, constants.EnvData.DEFAULT_ROLES, []string{"user"})
|
||||
assert.EqualValues(t, constants.EnvData.PROTECTED_ROLES, []string{"admin"})
|
||||
assert.EqualValues(t, constants.EnvData.ALLOWED_ORIGINS, []string{"*"})
|
||||
}
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/enum"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
|
@ -25,7 +27,7 @@ func logoutTests(s TestSetup, t *testing.T) {
|
|||
})
|
||||
|
||||
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)
|
||||
assert.Nil(t, err)
|
||||
_, err = resolvers.Profile(ctx)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/enum"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
|
@ -26,7 +28,7 @@ func magicLinkLoginTests(s TestSetup, t *testing.T) {
|
|||
})
|
||||
|
||||
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)
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package test
|
|||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"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) {
|
||||
ctx := context.Background()
|
||||
meta, err := resolvers.Meta(ctx)
|
||||
log.Println("=> meta:", meta)
|
||||
assert.Nil(t, err)
|
||||
assert.False(t, meta.IsFacebookLoginEnabled)
|
||||
assert.False(t, meta.IsGoogleLoginEnabled)
|
||||
|
|
|
@ -1,8 +1,10 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/enum"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
|
@ -30,7 +32,7 @@ func profileTests(s TestSetup, t *testing.T) {
|
|||
})
|
||||
|
||||
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)
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/enum"
|
||||
"github.com/authorizerdev/authorizer/server/env"
|
||||
)
|
||||
|
||||
func TestResolvers(t *testing.T) {
|
||||
|
@ -16,14 +18,36 @@ func TestResolvers(t *testing.T) {
|
|||
}
|
||||
|
||||
for dbType, dbURL := range databases {
|
||||
constants.DATABASE_URL = dbURL
|
||||
constants.DATABASE_TYPE = dbType
|
||||
constants.EnvData.DATABASE_URL = dbURL
|
||||
constants.EnvData.DATABASE_TYPE = dbType
|
||||
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()
|
||||
defer s.Server.Close()
|
||||
|
||||
log.Println("EnvData:", constants.EnvData)
|
||||
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)
|
||||
signupTests(s, t)
|
||||
forgotPasswordTest(s, t)
|
||||
|
@ -36,12 +60,6 @@ func TestResolvers(t *testing.T) {
|
|||
magicLinkLoginTests(s, t)
|
||||
logoutTests(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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/enum"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
|
@ -30,7 +32,8 @@ func sessionTests(s TestSetup, t *testing.T) {
|
|||
})
|
||||
|
||||
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{})
|
||||
assert.Nil(t, err)
|
||||
|
||||
|
|
|
@ -72,7 +72,8 @@ func testSetup() TestSetup {
|
|||
Password: "test",
|
||||
}
|
||||
|
||||
constants.ENV_PATH = "../../.env.sample"
|
||||
constants.EnvData.ENV_PATH = "../../.env.sample"
|
||||
|
||||
env.InitEnv()
|
||||
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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/db"
|
||||
"github.com/authorizerdev/authorizer/server/enum"
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
|
@ -33,7 +35,7 @@ func updateProfileTests(s TestSetup, t *testing.T) {
|
|||
})
|
||||
|
||||
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{
|
||||
FamilyName: &fName,
|
||||
})
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -29,7 +31,9 @@ func updateUserTest(s TestSetup, t *testing.T) {
|
|||
})
|
||||
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{
|
||||
ID: user.ID,
|
||||
Roles: newRoles,
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -22,7 +24,9 @@ func usersTest(s TestSetup, t *testing.T) {
|
|||
users, err := resolvers.Users(ctx)
|
||||
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)
|
||||
assert.Nil(t, err)
|
||||
rLen := len(users)
|
||||
|
|
|
@ -22,7 +22,7 @@ func TestIsValidEmail(t *testing.T) {
|
|||
func TestIsValidOrigin(t *testing.T) {
|
||||
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
|
||||
// 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://appgoogle.com"), "it should be invalid origin")
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
package test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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"
|
||||
)
|
||||
|
||||
|
@ -23,7 +25,9 @@ func verificationRequestsTest(s TestSetup, t *testing.T) {
|
|||
requests, err := resolvers.VerificationRequests(ctx)
|
||||
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)
|
||||
|
||||
assert.Nil(t, err)
|
||||
|
|
|
@ -1,51 +1,62 @@
|
|||
package constants
|
||||
|
||||
// this constants are configured via env
|
||||
var (
|
||||
ADMIN_SECRET = ""
|
||||
ENV = ""
|
||||
ENV_PATH = ""
|
||||
VERSION = ""
|
||||
DATABASE_TYPE = ""
|
||||
DATABASE_URL = ""
|
||||
DATABASE_NAME = ""
|
||||
SMTP_HOST = ""
|
||||
SMTP_PORT = ""
|
||||
SMTP_USERNAME = ""
|
||||
SMTP_PASSWORD = ""
|
||||
SENDER_EMAIL = ""
|
||||
JWT_TYPE = ""
|
||||
JWT_SECRET = ""
|
||||
ALLOWED_ORIGINS = []string{}
|
||||
AUTHORIZER_URL = ""
|
||||
APP_URL = ""
|
||||
PORT = ""
|
||||
REDIS_URL = ""
|
||||
IS_PROD = false
|
||||
COOKIE_NAME = ""
|
||||
RESET_PASSWORD_URL = ""
|
||||
DISABLE_EMAIL_VERIFICATION = false
|
||||
DISABLE_BASIC_AUTHENTICATION = false
|
||||
DISABLE_MAGIC_LINK_LOGIN = false
|
||||
DISABLE_LOGIN_PAGE = false
|
||||
type EnvConst struct {
|
||||
ADMIN_SECRET string
|
||||
ENV string
|
||||
ENV_PATH string
|
||||
VERSION string
|
||||
DATABASE_TYPE string
|
||||
DATABASE_URL string
|
||||
DATABASE_NAME string
|
||||
SMTP_HOST string
|
||||
SMTP_PORT string
|
||||
SMTP_PASSWORD string
|
||||
SMTP_USERNAME string
|
||||
SENDER_EMAIL string
|
||||
JWT_TYPE string
|
||||
JWT_SECRET string
|
||||
ALLOWED_ORIGINS []string
|
||||
AUTHORIZER_URL string
|
||||
APP_URL string
|
||||
PORT string
|
||||
REDIS_URL string
|
||||
COOKIE_NAME string
|
||||
ADMIN_COOKIE_NAME string
|
||||
RESET_PASSWORD_URL string
|
||||
ENCRYPTION_KEY string `json:"-"`
|
||||
IS_PROD bool
|
||||
DISABLE_EMAIL_VERIFICATION bool
|
||||
DISABLE_BASIC_AUTHENTICATION bool
|
||||
DISABLE_MAGIC_LINK_LOGIN bool
|
||||
DISABLE_LOGIN_PAGE bool
|
||||
|
||||
// ROLES
|
||||
ROLES = []string{}
|
||||
PROTECTED_ROLES = []string{}
|
||||
DEFAULT_ROLES = []string{}
|
||||
JWT_ROLE_CLAIM = "role"
|
||||
ROLES []string
|
||||
PROTECTED_ROLES []string
|
||||
DEFAULT_ROLES []string
|
||||
JWT_ROLE_CLAIM string
|
||||
|
||||
// OAuth login
|
||||
GOOGLE_CLIENT_ID = ""
|
||||
GOOGLE_CLIENT_SECRET = ""
|
||||
GITHUB_CLIENT_ID = ""
|
||||
GITHUB_CLIENT_SECRET = ""
|
||||
FACEBOOK_CLIENT_ID = ""
|
||||
FACEBOOK_CLIENT_SECRET = ""
|
||||
TWITTER_CLIENT_ID = ""
|
||||
TWITTER_CLIENT_SECRET = ""
|
||||
GOOGLE_CLIENT_ID string
|
||||
GOOGLE_CLIENT_SECRET string
|
||||
GITHUB_CLIENT_ID string
|
||||
GITHUB_CLIENT_SECRET string
|
||||
FACEBOOK_CLIENT_ID string
|
||||
FACEBOOK_CLIENT_SECRET string
|
||||
|
||||
// Org envs
|
||||
ORGANIZATION_NAME = "Authorizer"
|
||||
ORGANIZATION_LOGO = "https://authorizer.dev/images/logo.png"
|
||||
)
|
||||
ORGANIZATION_NAME string
|
||||
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) {
|
||||
ctx := context.Background()
|
||||
conn, err := http.NewConnection(http.ConnectionConfig{
|
||||
Endpoints: []string{constants.DATABASE_URL},
|
||||
Endpoints: []string{constants.EnvData.DATABASE_URL},
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -32,16 +32,16 @@ func initArangodb() (arangoDriver.Database, error) {
|
|||
|
||||
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 {
|
||||
log.Println(constants.DATABASE_NAME + " db exists already")
|
||||
arangodb, err = arangoClient.Database(nil, constants.DATABASE_NAME)
|
||||
log.Println(constants.EnvData.DATABASE_NAME + " db exists already")
|
||||
arangodb, err = arangoClient.Database(nil, constants.EnvData.DATABASE_NAME)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
arangodb, err = arangoClient.CreateDatabase(nil, constants.DATABASE_NAME, nil)
|
||||
arangodb, err = arangoClient.CreateDatabase(nil, constants.EnvData.DATABASE_NAME, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -100,5 +100,15 @@ func initArangodb() (arangoDriver.Database, error) {
|
|||
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
|
||||
}
|
||||
|
|
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)
|
||||
AddSession(session Session) error
|
||||
DeleteUserSession(userId string) error
|
||||
AddConfig(config Config) (Config, error)
|
||||
UpdateConfig(config Config) (Config, error)
|
||||
GetConfig() (Config, error)
|
||||
}
|
||||
|
||||
type manager struct {
|
||||
|
@ -42,6 +45,7 @@ type CollectionList struct {
|
|||
User string
|
||||
VerificationRequest string
|
||||
Session string
|
||||
Config string
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -54,6 +58,7 @@ var (
|
|||
User: Prefix + "users",
|
||||
VerificationRequest: Prefix + "verification_requests",
|
||||
Session: Prefix + "sessions",
|
||||
Config: Prefix + "config",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -61,9 +66,9 @@ func InitDB() {
|
|||
var sqlDB *gorm.DB
|
||||
var err error
|
||||
|
||||
IsORMSupported = constants.DATABASE_TYPE != enum.Arangodb.String() && constants.DATABASE_TYPE != enum.Mongodb.String()
|
||||
IsArangoDB = constants.DATABASE_TYPE == enum.Arangodb.String()
|
||||
IsMongoDB = constants.DATABASE_TYPE == enum.Mongodb.String()
|
||||
IsORMSupported = constants.EnvData.DATABASE_TYPE != enum.Arangodb.String() && constants.EnvData.DATABASE_TYPE != enum.Mongodb.String()
|
||||
IsArangoDB = constants.EnvData.DATABASE_TYPE == enum.Arangodb.String()
|
||||
IsMongoDB = constants.EnvData.DATABASE_TYPE == enum.Mongodb.String()
|
||||
|
||||
// sql db orm 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():
|
||||
sqlDB, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig)
|
||||
sqlDB, err = gorm.Open(postgres.Open(constants.EnvData.DATABASE_URL), ormConfig)
|
||||
break
|
||||
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
|
||||
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
|
||||
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
|
||||
case enum.Arangodb.String():
|
||||
arangodb, err := initArangodb()
|
||||
|
@ -118,7 +123,7 @@ func InitDB() {
|
|||
if err != nil {
|
||||
log.Fatal("Failed to init sqlDB:", err)
|
||||
} else {
|
||||
sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{})
|
||||
sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{}, &Config{})
|
||||
}
|
||||
Mgr = &manager{
|
||||
sqlDB: sqlDB,
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
)
|
||||
|
||||
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)
|
||||
mongodbOptions.ConnectTimeout = &maxWait
|
||||
mongoClient, err := mongo.NewClient(mongodbOptions)
|
||||
|
@ -30,7 +30,7 @@ func initMongodb() (*mongo.Database, error) {
|
|||
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())
|
||||
userCollection := mongodb.Collection(Collections.User, options.Collection())
|
||||
|
@ -73,5 +73,7 @@ func initMongodb() (*mongo.Database, error) {
|
|||
},
|
||||
}, options.CreateIndexes())
|
||||
|
||||
mongodb.CreateCollection(ctx, Collections.Config, options.CreateCollection())
|
||||
|
||||
return mongodb, nil
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ func (mgr *manager) AddUser(user User) (User, error) {
|
|||
}
|
||||
|
||||
if user.Roles == "" {
|
||||
user.Roles = constants.DEFAULT_ROLES[0]
|
||||
user.Roles = constants.EnvData.DEFAULT_ROLES[0]
|
||||
}
|
||||
|
||||
if IsORMSupported {
|
||||
|
|
|
@ -11,13 +11,13 @@ import (
|
|||
|
||||
func SendMail(to []string, Subject, bodyMessage string) error {
|
||||
m := gomail.NewMessage()
|
||||
m.SetHeader("From", constants.SENDER_EMAIL)
|
||||
m.SetHeader("From", constants.EnvData.SENDER_EMAIL)
|
||||
m.SetHeader("To", to...)
|
||||
m.SetHeader("Subject", Subject)
|
||||
m.SetBody("text/html", bodyMessage)
|
||||
port, _ := strconv.Atoi(constants.SMTP_PORT)
|
||||
d := gomail.NewDialer(constants.SMTP_HOST, port, constants.SMTP_USERNAME, constants.SMTP_PASSWORD)
|
||||
if constants.ENV == "development" {
|
||||
port, _ := strconv.Atoi(constants.EnvData.SMTP_PORT)
|
||||
d := gomail.NewDialer(constants.EnvData.SMTP_HOST, port, constants.EnvData.SMTP_USERNAME, constants.EnvData.SMTP_PASSWORD)
|
||||
if constants.EnvData.ENV == "development" {
|
||||
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||
}
|
||||
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/utils"
|
||||
"github.com/google/uuid"
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
|
@ -20,167 +21,173 @@ var (
|
|||
|
||||
// InitEnv -> to initialize env and through error if required env are not present
|
||||
func InitEnv() {
|
||||
if constants.ENV_PATH == "" {
|
||||
constants.ENV_PATH = `.env`
|
||||
if constants.EnvData.ENV_PATH == "" {
|
||||
constants.EnvData.ENV_PATH = `.env`
|
||||
}
|
||||
|
||||
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 {
|
||||
log.Printf("error loading %s file", constants.ENV_PATH)
|
||||
log.Printf("error loading %s file", constants.EnvData.ENV_PATH)
|
||||
}
|
||||
|
||||
if constants.ADMIN_SECRET == "" {
|
||||
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
|
||||
if constants.ADMIN_SECRET == "" {
|
||||
panic("root admin secret is required")
|
||||
}
|
||||
if constants.EnvData.ADMIN_SECRET == "" {
|
||||
constants.EnvData.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
|
||||
}
|
||||
|
||||
if constants.ENV == "" {
|
||||
constants.ENV = os.Getenv("ENV")
|
||||
if constants.ENV == "" {
|
||||
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 constants.EnvData.DATABASE_TYPE == "" {
|
||||
constants.EnvData.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
|
||||
log.Println(constants.EnvData.DATABASE_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")
|
||||
}
|
||||
}
|
||||
|
||||
if constants.DATABASE_URL == "" {
|
||||
constants.DATABASE_URL = os.Getenv("DATABASE_URL")
|
||||
if constants.EnvData.DATABASE_URL == "" {
|
||||
constants.EnvData.DATABASE_URL = os.Getenv("DATABASE_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")
|
||||
}
|
||||
}
|
||||
|
||||
if constants.DATABASE_NAME == "" {
|
||||
constants.DATABASE_NAME = os.Getenv("DATABASE_NAME")
|
||||
if constants.DATABASE_NAME == "" {
|
||||
constants.DATABASE_NAME = "authorizer"
|
||||
if constants.EnvData.DATABASE_NAME == "" {
|
||||
constants.EnvData.DATABASE_NAME = os.Getenv("DATABASE_NAME")
|
||||
if constants.EnvData.DATABASE_NAME == "" {
|
||||
constants.EnvData.DATABASE_NAME = "authorizer"
|
||||
}
|
||||
}
|
||||
|
||||
if constants.SMTP_HOST == "" {
|
||||
constants.SMTP_HOST = os.Getenv("SMTP_HOST")
|
||||
}
|
||||
if constants.EnvData.ENV == "" {
|
||||
constants.EnvData.ENV = os.Getenv("ENV")
|
||||
if constants.EnvData.ENV == "" {
|
||||
constants.EnvData.ENV = "production"
|
||||
}
|
||||
|
||||
if constants.SMTP_PORT == "" {
|
||||
constants.SMTP_PORT = os.Getenv("SMTP_PORT")
|
||||
}
|
||||
|
||||
if constants.SMTP_USERNAME == "" {
|
||||
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.EnvData.ENV == "production" {
|
||||
constants.EnvData.IS_PROD = true
|
||||
os.Setenv("GIN_MODE", "release")
|
||||
} else {
|
||||
constants.EnvData.IS_PROD = false
|
||||
}
|
||||
}
|
||||
|
||||
if constants.AUTHORIZER_URL == "" {
|
||||
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
|
||||
if constants.EnvData.SMTP_HOST == "" {
|
||||
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 != "" {
|
||||
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
|
||||
constants.EnvData.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
|
||||
}
|
||||
}
|
||||
|
||||
if constants.PORT == "" {
|
||||
constants.PORT = os.Getenv("PORT")
|
||||
if constants.PORT == "" {
|
||||
constants.PORT = "8080"
|
||||
if constants.EnvData.PORT == "" {
|
||||
constants.EnvData.PORT = os.Getenv("PORT")
|
||||
if constants.EnvData.PORT == "" {
|
||||
constants.EnvData.PORT = "8080"
|
||||
}
|
||||
}
|
||||
|
||||
if constants.REDIS_URL == "" {
|
||||
constants.REDIS_URL = os.Getenv("REDIS_URL")
|
||||
if constants.EnvData.REDIS_URL == "" {
|
||||
constants.EnvData.REDIS_URL = os.Getenv("REDIS_URL")
|
||||
}
|
||||
|
||||
if constants.COOKIE_NAME == "" {
|
||||
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME")
|
||||
if constants.EnvData.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 == "" {
|
||||
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
|
||||
if constants.EnvData.GOOGLE_CLIENT_ID == "" {
|
||||
constants.EnvData.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
|
||||
}
|
||||
|
||||
if constants.GOOGLE_CLIENT_SECRET == "" {
|
||||
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
|
||||
if constants.EnvData.GOOGLE_CLIENT_SECRET == "" {
|
||||
constants.EnvData.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
|
||||
}
|
||||
|
||||
if constants.GITHUB_CLIENT_ID == "" {
|
||||
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
|
||||
if constants.EnvData.GITHUB_CLIENT_ID == "" {
|
||||
constants.EnvData.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
|
||||
}
|
||||
|
||||
if constants.GITHUB_CLIENT_SECRET == "" {
|
||||
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
|
||||
if constants.EnvData.GITHUB_CLIENT_SECRET == "" {
|
||||
constants.EnvData.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
|
||||
}
|
||||
|
||||
if constants.FACEBOOK_CLIENT_ID == "" {
|
||||
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
|
||||
if constants.EnvData.FACEBOOK_CLIENT_ID == "" {
|
||||
constants.EnvData.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
|
||||
}
|
||||
|
||||
if constants.FACEBOOK_CLIENT_SECRET == "" {
|
||||
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
|
||||
if constants.EnvData.FACEBOOK_CLIENT_SECRET == "" {
|
||||
constants.EnvData.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
|
||||
}
|
||||
|
||||
if constants.RESET_PASSWORD_URL == "" {
|
||||
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
|
||||
if constants.EnvData.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.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
|
||||
constants.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
|
||||
constants.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true"
|
||||
constants.EnvData.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
|
||||
constants.EnvData.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
|
||||
constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "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 == "" {
|
||||
constants.DISABLE_EMAIL_VERIFICATION = true
|
||||
constants.DISABLE_MAGIC_LINK_LOGIN = true
|
||||
if constants.EnvData.SMTP_HOST == "" || constants.EnvData.SMTP_USERNAME == "" || constants.EnvData.SMTP_PASSWORD == "" || constants.EnvData.SENDER_EMAIL == "" {
|
||||
constants.EnvData.DISABLE_EMAIL_VERIFICATION = true
|
||||
constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true
|
||||
}
|
||||
|
||||
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
|
||||
|
@ -209,18 +216,10 @@ func InitEnv() {
|
|||
allowedOrigins = []string{"*"}
|
||||
}
|
||||
|
||||
constants.ALLOWED_ORIGINS = allowedOrigins
|
||||
constants.EnvData.ALLOWED_ORIGINS = allowedOrigins
|
||||
|
||||
if constants.JWT_TYPE == "" {
|
||||
constants.JWT_TYPE = "HS256"
|
||||
}
|
||||
|
||||
if constants.COOKIE_NAME == "" {
|
||||
constants.COOKIE_NAME = "authorizer"
|
||||
}
|
||||
|
||||
if constants.DISABLE_EMAIL_VERIFICATION {
|
||||
constants.DISABLE_MAGIC_LINK_LOGIN = true
|
||||
if constants.EnvData.DISABLE_EMAIL_VERIFICATION {
|
||||
constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true
|
||||
}
|
||||
|
||||
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`)
|
||||
}
|
||||
|
||||
constants.ROLES = roles
|
||||
constants.DEFAULT_ROLES = defaultRoles
|
||||
constants.PROTECTED_ROLES = protectedRoles
|
||||
constants.EnvData.ROLES = roles
|
||||
constants.EnvData.DEFAULT_ROLES = defaultRoles
|
||||
constants.EnvData.PROTECTED_ROLES = protectedRoles
|
||||
|
||||
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") != "" {
|
||||
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/reflect2 v1.0.1 // indirect
|
||||
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/vektah/gqlparser/v2 v2.2.0
|
||||
go.mongodb.org/mongo-driver v1.8.1
|
||||
|
@ -30,7 +29,7 @@ require (
|
|||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // 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
|
||||
gorm.io/driver/mysql v1.2.1
|
||||
gorm.io/driver/postgres v1.2.3
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,6 +2,14 @@
|
|||
|
||||
package model
|
||||
|
||||
type AdminLoginInput struct {
|
||||
AdminSecret string `json:"admin_secret"`
|
||||
}
|
||||
|
||||
type AdminSignupInput struct {
|
||||
AdminSecret string `json:"admin_secret"`
|
||||
}
|
||||
|
||||
type AuthResponse struct {
|
||||
Message string `json:"message"`
|
||||
AccessToken *string `json:"access_token"`
|
||||
|
@ -9,6 +17,42 @@ type AuthResponse struct {
|
|||
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 {
|
||||
Email string `json:"email"`
|
||||
}
|
||||
|
@ -73,6 +117,41 @@ type SignUpInput struct {
|
|||
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 {
|
||||
OldPassword *string `json:"old_password"`
|
||||
NewPassword *string `json:"new_password"`
|
||||
|
|
|
@ -62,6 +62,85 @@ type Response {
|
|||
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 {
|
||||
email: String!
|
||||
given_name: String
|
||||
|
@ -153,13 +232,19 @@ type Mutation {
|
|||
# admin only apis
|
||||
_delete_user(params: DeleteUserInput!): Response!
|
||||
_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 {
|
||||
meta: Meta!
|
||||
session(roles: [String!]): AuthResponse
|
||||
session(roles: [String!]): AuthResponse!
|
||||
profile: User!
|
||||
# admin only apis
|
||||
_users: [User!]!
|
||||
_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)
|
||||
}
|
||||
|
||||
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) {
|
||||
return resolvers.Meta(ctx)
|
||||
}
|
||||
|
@ -75,13 +91,19 @@ func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.Veri
|
|||
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.
|
||||
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
|
||||
|
||||
// Query returns generated.QueryResolver implementation.
|
||||
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
type (
|
||||
mutationResolver struct{ *Resolver }
|
||||
queryResolver struct{ *Resolver }
|
||||
)
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
|
@ -30,17 +29,17 @@ func AppHandler() gin.HandlerFunc {
|
|||
// return
|
||||
// }
|
||||
|
||||
stateObj.AuthorizerURL = constants.AUTHORIZER_URL
|
||||
stateObj.RedirectURL = constants.AUTHORIZER_URL + "/app"
|
||||
stateObj.AuthorizerURL = constants.EnvData.AUTHORIZER_URL
|
||||
stateObj.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/app"
|
||||
|
||||
} else {
|
||||
decodedState, err := base64.StdEncoding.DecodeString(state)
|
||||
decodedState, err := utils.DecryptB64(state)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(decodedState, &stateObj)
|
||||
err = json.Unmarshal([]byte(decodedState), &stateObj)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
|
||||
return
|
||||
|
@ -60,7 +59,7 @@ func AppHandler() gin.HandlerFunc {
|
|||
}
|
||||
|
||||
// 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"})
|
||||
return
|
||||
}
|
||||
|
@ -77,8 +76,8 @@ func AppHandler() gin.HandlerFunc {
|
|||
"data": map[string]string{
|
||||
"authorizerURL": stateObj.AuthorizerURL,
|
||||
"redirectURL": stateObj.RedirectURL,
|
||||
"organizationName": constants.ORGANIZATION_NAME,
|
||||
"organizationLogo": constants.ORGANIZATION_LOGO,
|
||||
"organizationName": constants.EnvData.ORGANIZATION_NAME,
|
||||
"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
|
||||
hasProtectedRole := false
|
||||
for _, ir := range inputRoles {
|
||||
if utils.StringSliceContains(constants.PROTECTED_ROLES, ir) {
|
||||
if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ir) {
|
||||
hasProtectedRole = true
|
||||
}
|
||||
}
|
||||
|
@ -238,7 +238,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
|||
// check if it contains protected unassigned role
|
||||
hasProtectedRole := false
|
||||
for _, ur := range unasignedRoles {
|
||||
if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) {
|
||||
if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) {
|
||||
hasProtectedRole = true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,14 +34,14 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||
|
||||
// use protected roles verification for admin login only.
|
||||
// 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{
|
||||
"error": "invalid role",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
roles = strings.Join(constants.DEFAULT_ROLES, ",")
|
||||
roles = strings.Join(constants.EnvData.DEFAULT_ROLES, ",")
|
||||
}
|
||||
|
||||
uuid := uuid.New()
|
||||
|
@ -57,7 +57,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||
}
|
||||
session.SetSocailLoginState(oauthStateString, enum.Google.String())
|
||||
// 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)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
case enum.Github.String():
|
||||
|
@ -66,7 +66,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||
break
|
||||
}
|
||||
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)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
case enum.Facebook.String():
|
||||
|
@ -75,7 +75,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
|||
break
|
||||
}
|
||||
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)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
default:
|
||||
|
|
|
@ -22,20 +22,22 @@ func main() {
|
|||
env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
|
||||
flag.Parse()
|
||||
|
||||
constants.VERSION = VERSION
|
||||
constants.EnvData.VERSION = VERSION
|
||||
|
||||
env.InitEnv()
|
||||
db.InitDB()
|
||||
env.PersistEnv()
|
||||
|
||||
session.InitSession()
|
||||
oauth.InitOAuth()
|
||||
utils.InitServer()
|
||||
|
||||
router := router.InitRouter()
|
||||
|
||||
router.LoadHTMLGlob("templates/*")
|
||||
// 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 !constants.DISABLE_LOGIN_PAGE {
|
||||
router.LoadHTMLGlob("templates/*")
|
||||
if !constants.EnvData.DISABLE_LOGIN_PAGE {
|
||||
app := router.Group("/app")
|
||||
{
|
||||
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 {
|
||||
return func(c *gin.Context) {
|
||||
if constants.AUTHORIZER_URL == "" {
|
||||
if constants.EnvData.AUTHORIZER_URL == "" {
|
||||
url := location.Get(c)
|
||||
constants.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host
|
||||
log.Println("authorizer url:", constants.AUTHORIZER_URL)
|
||||
constants.EnvData.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host
|
||||
log.Println("authorizer url:", constants.EnvData.AUTHORIZER_URL)
|
||||
}
|
||||
ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
|
||||
c.Request = c.Request.WithContext(ctx)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package middlewares
|
||||
|
||||
import (
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
@ -9,7 +8,6 @@ import (
|
|||
func CORSMiddleware() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
origin := c.Request.Header.Get("Origin")
|
||||
constants.APP_URL = origin
|
||||
|
||||
if utils.IsValidOrigin(origin) {
|
||||
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
|
||||
|
|
|
@ -28,33 +28,33 @@ var (
|
|||
|
||||
func InitOAuth() {
|
||||
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")
|
||||
if err != nil {
|
||||
log.Fatalln("error creating oidc provider for google:", err)
|
||||
}
|
||||
OIDCProviders.GoogleOIDC = p
|
||||
OAuthProviders.GoogleConfig = &oauth2.Config{
|
||||
ClientID: constants.GOOGLE_CLIENT_ID,
|
||||
ClientSecret: constants.GOOGLE_CLIENT_SECRET,
|
||||
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/google",
|
||||
ClientID: constants.EnvData.GOOGLE_CLIENT_ID,
|
||||
ClientSecret: constants.EnvData.GOOGLE_CLIENT_SECRET,
|
||||
RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google",
|
||||
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
|
||||
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{
|
||||
ClientID: constants.GITHUB_CLIENT_ID,
|
||||
ClientSecret: constants.GITHUB_CLIENT_SECRET,
|
||||
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/github",
|
||||
ClientID: constants.EnvData.GITHUB_CLIENT_ID,
|
||||
ClientSecret: constants.EnvData.GITHUB_CLIENT_SECRET,
|
||||
RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github",
|
||||
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{
|
||||
ClientID: constants.FACEBOOK_CLIENT_ID,
|
||||
ClientSecret: constants.FACEBOOK_CLIENT_SECRET,
|
||||
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/facebook",
|
||||
ClientID: constants.EnvData.FACEBOOK_CLIENT_ID,
|
||||
ClientSecret: constants.EnvData.FACEBOOK_CLIENT_SECRET,
|
||||
RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/facebook",
|
||||
Endpoint: facebookOAuth2.Endpoint,
|
||||
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 {
|
||||
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`)
|
||||
}
|
||||
host := gc.Request.Host
|
||||
|
|
|
@ -22,7 +22,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
|
|||
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`)
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
|
|||
log.Println("compare password error:", err)
|
||||
return res, fmt.Errorf(`invalid password`)
|
||||
}
|
||||
roles := constants.DEFAULT_ROLES
|
||||
roles := constants.EnvData.DEFAULT_ROLES
|
||||
currentRoles := strings.Split(user.Roles, ",")
|
||||
if len(params.Roles) > 0 {
|
||||
if !utils.IsValidRoles(currentRoles, params.Roles) {
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
|
||||
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`)
|
||||
}
|
||||
|
||||
|
@ -41,13 +41,13 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod
|
|||
// define roles for new user
|
||||
if len(params.Roles) > 0 {
|
||||
// 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`)
|
||||
} else {
|
||||
inputRoles = params.Roles
|
||||
}
|
||||
} else {
|
||||
inputRoles = constants.DEFAULT_ROLES
|
||||
inputRoles = constants.EnvData.DEFAULT_ROLES
|
||||
}
|
||||
|
||||
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
|
||||
hasProtectedRole := false
|
||||
for _, ur := range unasignedRoles {
|
||||
if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) {
|
||||
if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) {
|
||||
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
|
||||
verificationType := enum.MagicLinkLogin.String()
|
||||
token, err := utils.CreateVerificationToken(params.Email, verificationType)
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
|
||||
func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
|
||||
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`)
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
|
|||
expiresTimeObj := time.Unix(expiresAt, 0)
|
||||
currentTimeObj := time.Now()
|
||||
|
||||
claimRoleInterface := claim[constants.JWT_ROLE_CLAIM].([]interface{})
|
||||
claimRoleInterface := claim[constants.EnvData.JWT_ROLE_CLAIM].([]interface{})
|
||||
claimRoles := make([]string, len(claimRoleInterface))
|
||||
for i, v := range claimRoleInterface {
|
||||
claimRoles[i] = v.(string)
|
||||
|
|
|
@ -22,7 +22,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
|
|||
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`)
|
||||
}
|
||||
if params.ConfirmPassword != params.Password {
|
||||
|
@ -52,13 +52,13 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
|
|||
|
||||
if len(params.Roles) > 0 {
|
||||
// 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`)
|
||||
} else {
|
||||
inputRoles = params.Roles
|
||||
}
|
||||
} else {
|
||||
inputRoles = constants.DEFAULT_ROLES
|
||||
inputRoles = constants.EnvData.DEFAULT_ROLES
|
||||
}
|
||||
|
||||
user := db.User{
|
||||
|
@ -103,7 +103,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
|
|||
}
|
||||
|
||||
user.SignupMethods = enum.BasicAuth.String()
|
||||
if constants.DISABLE_EMAIL_VERIFICATION {
|
||||
if constants.EnvData.DISABLE_EMAIL_VERIFICATION {
|
||||
now := time.Now().Unix()
|
||||
user.EmailVerifiedAt = &now
|
||||
}
|
||||
|
@ -115,7 +115,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
|
|||
roles := strings.Split(user.Roles, ",")
|
||||
userToReturn := utils.GetResponseUserData(user)
|
||||
|
||||
if !constants.DISABLE_EMAIL_VERIFICATION {
|
||||
if !constants.EnvData.DISABLE_EMAIL_VERIFICATION {
|
||||
// insert verification request
|
||||
verificationType := enum.BasicAuthSignup.String()
|
||||
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)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
|
|
|
@ -95,9 +95,9 @@ func RemoveSocialLoginState(key string) {
|
|||
}
|
||||
|
||||
func InitSession() {
|
||||
if constants.REDIS_URL != "" {
|
||||
if constants.EnvData.REDIS_URL != "" {
|
||||
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 {
|
||||
log.Fatalln("Error parsing redis url:", err)
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -14,10 +15,11 @@ import (
|
|||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt"
|
||||
"github.com/robertkrimen/otto"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
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
|
||||
if tokenType == enum.RefreshToken {
|
||||
// expires in 1 year
|
||||
|
@ -32,11 +34,11 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
|
|||
json.Unmarshal(userBytes, &userMap)
|
||||
|
||||
customClaims := jwt.MapClaims{
|
||||
"exp": expiresAt,
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": tokenType.String(),
|
||||
"allowed_roles": strings.Split(user.Roles, ","),
|
||||
constants.JWT_ROLE_CLAIM: roles,
|
||||
"exp": expiresAt,
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": tokenType.String(),
|
||||
"allowed_roles": strings.Split(user.Roles, ","),
|
||||
constants.EnvData.JWT_ROLE_CLAIM: roles,
|
||||
}
|
||||
|
||||
for k, v := range userMap {
|
||||
|
@ -77,7 +79,7 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
|
|||
|
||||
t.Claims = customClaims
|
||||
|
||||
token, err := t.SignedString([]byte(constants.JWT_SECRET))
|
||||
token, err := t.SignedString([]byte(constants.EnvData.JWT_SECRET))
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
|
@ -89,7 +91,6 @@ func GetAuthToken(gc *gin.Context) (string, error) {
|
|||
token, err := GetCookie(gc)
|
||||
if err != nil || token == "" {
|
||||
// try to check in auth header for cookie
|
||||
log.Println("cookie not found checking headers")
|
||||
auth := gc.Request.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return "", fmt.Errorf(`unauthorized`)
|
||||
|
@ -105,7 +106,7 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
|
|||
claims := jwt.MapClaims{}
|
||||
|
||||
_, 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 {
|
||||
return res, err
|
||||
|
@ -124,3 +125,29 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
|
|||
|
||||
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) {
|
||||
secure := true
|
||||
httpOnly := true
|
||||
host, _ := GetHostParts(constants.AUTHORIZER_URL)
|
||||
domain := GetDomainName(constants.AUTHORIZER_URL)
|
||||
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
|
||||
domain := GetDomainName(constants.EnvData.AUTHORIZER_URL)
|
||||
if domain != "localhost" {
|
||||
domain = "." + domain
|
||||
}
|
||||
|
||||
gc.SetSameSite(http.SameSiteNoneMode)
|
||||
gc.SetCookie(constants.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
|
||||
gc.SetCookie(constants.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly)
|
||||
gc.SetCookie(constants.EnvData.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
|
||||
gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly)
|
||||
}
|
||||
|
||||
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 {
|
||||
cookie, err = gc.Request.Cookie(constants.COOKIE_NAME + "-client")
|
||||
cookie, err = gc.Request.Cookie(constants.EnvData.COOKIE_NAME + "-client")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
@ -37,13 +37,37 @@ func DeleteCookie(gc *gin.Context) {
|
|||
secure := true
|
||||
httpOnly := true
|
||||
|
||||
host, _ := GetHostParts(constants.AUTHORIZER_URL)
|
||||
domain := GetDomainName(constants.AUTHORIZER_URL)
|
||||
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
|
||||
domain := GetDomainName(constants.EnvData.AUTHORIZER_URL)
|
||||
if domain != "localhost" {
|
||||
domain = "." + domain
|
||||
}
|
||||
|
||||
gc.SetSameSite(http.SameSiteNoneMode)
|
||||
gc.SetCookie(constants.COOKIE_NAME, "", -1, "/", host, secure, httpOnly)
|
||||
gc.SetCookie(constants.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly)
|
||||
gc.SetCookie(constants.EnvData.COOKIE_NAME, "", -1, "/", host, 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>
|
||||
`
|
||||
data := make(map[string]interface{}, 3)
|
||||
data["OrgLogo"] = constants.ORGANIZATION_LOGO
|
||||
data["OrgName"] = constants.ORGANIZATION_NAME
|
||||
data["AuthUrl"] = constants.AUTHORIZER_URL + "/verify_email?token=" + token
|
||||
data["OrgLogo"] = constants.EnvData.ORGANIZATION_LOGO
|
||||
data["OrgName"] = constants.EnvData.ORGANIZATION_NAME
|
||||
data["AuthUrl"] = constants.EnvData.AUTHORIZER_URL + "/verify_email?token=" + token
|
||||
message = AddEmailTemplate(message, data, "verify_email.tmpl")
|
||||
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
|
||||
|
||||
|
@ -112,8 +112,8 @@ func SendVerificationMail(toEmail, token string) error {
|
|||
|
||||
// SendForgotPasswordMail to send verification email
|
||||
func SendForgotPasswordMail(toEmail, token, host string) error {
|
||||
if constants.RESET_PASSWORD_URL == "" {
|
||||
constants.RESET_PASSWORD_URL = constants.AUTHORIZER_URL + "/app/reset-password"
|
||||
if constants.EnvData.RESET_PASSWORD_URL == "" {
|
||||
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
|
||||
|
@ -207,9 +207,9 @@ func SendForgotPasswordMail(toEmail, token, host string) error {
|
|||
`
|
||||
|
||||
data := make(map[string]interface{}, 3)
|
||||
data["OrgLogo"] = constants.ORGANIZATION_LOGO
|
||||
data["ToEmail"] = constants.ORGANIZATION_NAME
|
||||
data["AuthUrl"] = constants.RESET_PASSWORD_URL + "?token=" + token
|
||||
data["OrgLogo"] = constants.EnvData.ORGANIZATION_LOGO
|
||||
data["ToEmail"] = constants.EnvData.ORGANIZATION_NAME
|
||||
data["AuthUrl"] = constants.EnvData.RESET_PASSWORD_URL + "?token=" + token
|
||||
message = AddEmailTemplate(message, data, "reset_password_email.tmpl")
|
||||
|
||||
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 {
|
||||
if len(constants.ALLOWED_ORIGINS) == 1 && constants.ALLOWED_ORIGINS[0] == "*" {
|
||||
if len(constants.EnvData.ALLOWED_ORIGINS) == 1 && constants.EnvData.ALLOWED_ORIGINS[0] == "*" {
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ func IsValidOrigin(url string) bool {
|
|||
hostName, port := GetHostParts(url)
|
||||
currentOrigin := hostName + ":" + port
|
||||
|
||||
for _, origin := range constants.ALLOWED_ORIGINS {
|
||||
for _, origin := range constants.EnvData.ALLOWED_ORIGINS {
|
||||
replacedString := origin
|
||||
// if has regex whitelisted domains
|
||||
if strings.Contains(origin, "*") {
|
||||
|
@ -50,12 +50,17 @@ func IsValidOrigin(url string) bool {
|
|||
}
|
||||
|
||||
func IsSuperAdmin(gc *gin.Context) bool {
|
||||
secret := gc.Request.Header.Get("x-authorizer-admin-secret")
|
||||
if secret == "" {
|
||||
return false
|
||||
token, err := GetAdminAuthToken(gc)
|
||||
if err != nil {
|
||||
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 {
|
||||
|
|
|
@ -20,23 +20,23 @@ type CustomClaim struct {
|
|||
}
|
||||
|
||||
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{
|
||||
&jwt.StandardClaims{
|
||||
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
|
||||
},
|
||||
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) {
|
||||
claims := &CustomClaim{}
|
||||
_, 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 {
|
||||
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