Compare commits

...

48 Commits

Author SHA1 Message Date
Lakhan Samani
e170569959 Add fauna db provider 2022-01-24 18:53:53 +05:30
Lakhan Samani
b42cc1549a fix: env to return custom access token script 2022-01-24 10:22:55 +05:30
Lakhan Samani
4bc9059b0f fix: allow using cookie and header in case of validating jwt 2022-01-24 09:56:12 +05:30
Lakhan Samani
87b1cac979 feat(server): add is_valid_jwt query 2022-01-24 00:32:06 +05:30
Lakhan Samani
7f18a3f634 Implement refresh token logic with fingerprint + rotation 2022-01-23 01:24:41 +05:30
Lakhan Samani
0511e737ae fix(server): add update roles env validation 2022-01-22 11:29:03 +05:30
Lakhan Samani
003d88fb6c Merge pull request #106 from authorizerdev/fix/organize_dbs
fix: organize dbs
2022-01-21 13:37:37 +05:30
Lakhan Samani
515b72f484 fix: cleaning of test 2022-01-21 13:36:19 +05:30
Lakhan Samani
cb96d2d8d1 fix: update to use db.Provider 2022-01-21 13:34:04 +05:30
Lakhan Samani
8a4b2feffe fix: user + verification requests to new db format 2022-01-21 12:53:30 +05:30
Lakhan Samani
13c038effd fix: env + session to new db format 2022-01-21 12:18:07 +05:30
Lakhan Samani
38419a4ef4 fix: rename config -> env and handle env interface better 2022-01-20 16:52:37 +05:30
Lakhan Samani
7785f98dcd fix(server): env setup 2022-01-19 23:19:20 +05:30
Lakhan Samani
5ecc49f861 fix(doc): merge conflict in contributing 2022-01-19 22:47:06 +05:30
Lakhan Samani
3cb02dd62c fix(dashboard): update home page text 2022-01-19 22:43:07 +05:30
Lakhan Samani
ddda237178 fix(dashboard): layout 2022-01-19 22:20:25 +05:30
Lakhan Samani
3b4d0d9769 fix(server): add old secret check for admin secret update 2022-01-17 13:20:32 +05:30
Lakhan Samani
c15b65b473 fix(server): rename config -> env 2022-01-17 13:12:46 +05:30
Lakhan Samani
e07448f670 fix(dashboard): remove unused var 2022-01-17 13:03:57 +05:30
Lakhan Samani
a596d91ce0 fix(dashboard): navigation issues 2022-01-17 13:03:28 +05:30
Lakhan Samani
f1b4141367 Feat/dashboard (#105) 2022-01-17 11:32:13 +05:30
Lakhan Samani
7ce96367a3 Merge pull request #104 from jyash97/yash/dashboard
feat: setup authorizer dashboard
2022-01-17 11:01:19 +05:30
Lakhan Samani
974622b9be Merge branch 'main' into yash/dashboard 2022-01-17 11:00:54 +05:30
Yash Joshi
8bee841d66 feat: setup dashboard
- Setup basic code structure
- Add routes
- Add layout components for authentication and dashboard pages
- Add session handling
- Add login, signup and session
2022-01-15 21:15:46 +05:30
Lakhan Samani
9363d83945 Update contributing test doc 2022-01-14 11:59:11 +05:30
Lakhan Samani
75affcbf30 Update contributing test doc 2022-01-14 11:59:11 +05:30
Lakhan Samani
f5aeda1283 Update arangodb test connection string 2022-01-14 11:59:11 +05:30
Lakhan Samani
3d75a4a281 Fix bug getting github login meta data 2022-01-10 10:45:20 +05:30
Lakhan Samani
f9ed91934e Update response + input types for admin apis 2022-01-09 18:40:30 +05:30
Lakhan Samani
266b9e34b6 Remove access_token from response 2022-01-09 18:02:16 +05:30
Lakhan Samani
047e867faa Add admin session only via http cookies 2022-01-09 17:40:08 +05:30
Lakhan Samani
9d08d6c672 Add _admin_signup mutation 2022-01-09 17:35:37 +05:30
Lakhan Samani
91c35aa381 Merge branch 'main' into feat/dashboard 2022-01-09 15:04:08 +05:30
Lakhan Samani
8e85d0ddbd Update test 2022-01-08 19:02:00 +05:30
Lakhan Samani
bb4052d1d2 Merge branch 'main' into feat/dashboard 2022-01-08 18:46:44 +05:30
Lakhan Samani
e0ae6aa2e0 Update dashboard 2022-01-08 15:46:39 +05:30
Lakhan Samani
ca716ec1dd Merge branch 'main' of https://github.com/authorizerdev/authorizer into feat/dashboard 2022-01-08 11:43:12 +05:30
Lakhan Samani
818790650a fix: enable test for mongo & arango 2022-01-01 08:41:44 +05:30
Lakhan Samani
eb5041008d Merge pull request #93 from authorizerdev/feat/admin-logout
Feat/admin logout
2021-12-31 23:07:41 +05:30
Lakhan Samani
152ab6dfd5 feat: add admin logout 2021-12-31 23:06:06 +05:30
Lakhan Samani
192070c18e Merge pull request #92 from authorizerdev/feat/setup-onboard-apis
feat/setup onboard apis
2021-12-31 17:27:22 +05:30
Lakhan Samani
f7f1a3e4b3 feat: add api for getting configurations 2021-12-31 17:24:22 +05:30
Lakhan Samani
9c8e9baa39 feat: add _update_config mutation 2021-12-31 17:03:37 +05:30
Lakhan Samani
217410e9a4 feat: add admin session api 2021-12-31 14:28:00 +05:30
Lakhan Samani
e35d0cbcd6 feat: persist encrypted env 2021-12-31 13:52:10 +05:30
Lakhan Samani
d9c40057e6 feat: add api for admin login 2021-12-30 10:01:51 +05:30
Lakhan Samani
86bcb8ca87 Merge pull request #91 from authorizerdev/feat/dashboard-setup
feat: add dashboard setup with esbuild + chakra-ui
2021-12-29 12:05:34 +05:30
Lakhan Samani
cf4d94a7aa feat: add dashboard setup with esbuild + chakra-ui 2021-12-29 11:56:19 +05:30
185 changed files with 10689 additions and 2978 deletions

View File

@@ -60,181 +60,170 @@ Setup mongodb & arangodb using Docker
``` ```
docker run --name mongodb -d -p 27017:27017 mongo docker run --name mongodb -d -p 27017:27017 mongo
docker run --name arangodb -d -p 8529:8529 -e ARANGO_ROOT_PASSWORD=root arangodb/arangodb:3.8.4
// -e ARANGO_ROOT_PASSWORD=root
docker run --name arangodb -d -p 8529:8529 -e ARANGO_NO_AUTH=1 arangodb/arangodb:3.8.4
``` ```
> Note: If you are not making any changes in db schema / db operations, you can disable those db tests [here](https://github.com/authorizerdev/authorizer/blob/main/server/__test__/resolvers_test.go#L14) > Note: If you are not making any changes in db schema / db operations, you can disable those db tests [here](https://github.com/authorizerdev/authorizer/blob/main/server/__test__/resolvers_test.go#L14)
If you are adding new resolver, If you are adding new resolver,
1. create new resolver test file [here](https://github.com/authorizerdev/authorizer/tree/main/server/__test__) 1. create new resolver test file [here](https://github.com/authorizerdev/authorizer/tree/main/server/__test__)
Naming convention filename: `resolver_name_test.go` function name: `resolverNameTest(s TestSetup, t *testing.T)` Naming convention filename: `resolver_name_test.go` function name: `resolverNameTest(t *testing.T, s TestSetup)`
2. Add your tests [here](https://github.com/authorizerdev/authorizer/blob/main/server/__test__/resolvers_test.go#L38) 2. Add your tests [here](https://github.com/authorizerdev/authorizer/blob/main/server/__test__/resolvers_test.go#L38)
__Command to run tests:__ **Command to run tests:**
```sh ```sh
make test make test
``` ```
__Manual Testing:__ **Manual Testing:**
For manually testing using graphql playground, you can paste following queries and mutations in your playground and test it For manually testing using graphql playground, you can paste following queries and mutations in your playground and test it
```gql ```gql
mutation Signup { mutation Signup {
signup(params: { signup(
email: "lakhan@yopmail.com", params: {
password: "test", email: "lakhan@yopmail.com"
confirm_password: "test", password: "test"
given_name: "lakhan" confirm_password: "test"
}) { given_name: "lakhan"
message }
user { ) {
id message
family_name user {
given_name id
email family_name
email_verified given_name
} email
} email_verified
}
}
} }
mutation ResendEamil { mutation ResendEamil {
resend_verify_email(params: { resend_verify_email(
email: "lakhan@yopmail.com" params: { email: "lakhan@yopmail.com", identifier: "basic_auth_signup" }
identifier: "basic_auth_signup" ) {
}) { message
message }
}
} }
query GetVerifyRequests { query GetVerifyRequests {
_verification_requests { _verification_requests {
id id
token token
expires expires
identifier identifier
} }
} }
mutation VerifyEmail { mutation VerifyEmail {
verify_email(params: { verify_email(params: { token: "" }) {
token: "" access_token
}) { expires_at
access_token user {
expires_at id
user { email
id given_name
email email_verified
given_name }
email_verified }
}
}
} }
mutation Login { mutation Login {
login(params: { login(params: { email: "lakhan@yopmail.com", password: "test" }) {
email: "lakhan@yopmail.com", access_token
password: "test" expires_at
}) { user {
access_token id
expires_at family_name
user { given_name
id email
family_name }
given_name }
email
}
}
} }
query GetSession { query GetSession {
session { session {
access_token access_token
expires_at expires_at
user { user {
id id
given_name given_name
family_name family_name
email email
email_verified email_verified
signup_methods signup_methods
created_at created_at
updated_at updated_at
} }
} }
} }
mutation ForgotPassword { mutation ForgotPassword {
forgot_password(params: { forgot_password(params: { email: "lakhan@yopmail.com" }) {
email: "lakhan@yopmail.com" message
}) { }
message
}
} }
mutation ResetPassword { mutation ResetPassword {
reset_password(params: { reset_password(
token: "" params: { token: "", password: "test", confirm_password: "test" }
password: "test" ) {
confirm_password: "test" message
}) { }
message
}
} }
mutation UpdateProfile { mutation UpdateProfile {
update_profile(params: { update_profile(params: { family_name: "samani" }) {
family_name: "samani" message
}) { }
message
}
} }
query GetUsers { query GetUsers {
_users { _users {
id id
email email
email_verified email_verified
given_name given_name
family_name family_name
picture picture
signup_methods signup_methods
phone_number phone_number
} }
} }
mutation MagicLinkLogin { mutation MagicLinkLogin {
magic_link_login(params: { magic_link_login(params: { email: "test@yopmail.com" }) {
email: "test@yopmail.com" message
}) { }
message
}
} }
mutation Logout { mutation Logout {
logout { logout {
message message
} }
} }
mutation UpdateUser{ mutation UpdateUser {
_update_user(params: { _update_user(
id: "dafc9400-d603-4ade-997c-83fcd54bbd67", params: {
roles: ["user", "admin"] id: "dafc9400-d603-4ade-997c-83fcd54bbd67"
}) { roles: ["user", "admin"]
email }
roles ) {
} email
roles
}
} }
mutation DeleteUser { mutation DeleteUser {
_delete_user(params: { _delete_user(params: { email: "signup.test134523@yopmail.com" }) {
email: "signup.test134523@yopmail.com" message
}) { }
message
}
} }
``` ```

View File

@@ -23,7 +23,8 @@ jobs:
sudo mv github-assets-uploader /usr/sbin/ && \ sudo mv github-assets-uploader /usr/sbin/ && \
sudo rm -f github-assets-uploader.tar.gz && \ sudo rm -f github-assets-uploader.tar.gz && \
github-assets-uploader -version && \ github-assets-uploader -version && \
make build-app make build-app && \
make build-dashboard
- name: Print Go paths - name: Print Go paths
run: whereis go run: whereis go
- name: Print Go Version - name: Print Go Version
@@ -37,12 +38,12 @@ jobs:
make clean && \ make clean && \
CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \ CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
mv build/server build/server.exe && \ mv build/server build/server.exe && \
zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates dashboard/build
- name: Package files for linux - name: Package files for linux
run: | run: |
make clean && \ make clean && \
CGO_ENABLED=1 make && \ CGO_ENABLED=1 make && \
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates dashboard/build
- name: Upload assets - name: Upload assets
run: | run: |
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \ github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \

2
.gitignore vendored
View File

@@ -3,6 +3,8 @@ server/.env
data data
app/node_modules app/node_modules
app/build app/build
dashboard/node_modules
dashboard/build
build build
.env .env
data.db data.db

View File

@@ -14,14 +14,17 @@ RUN apk add build-base &&\
FROM node:17-alpine3.12 as node-builder FROM node:17-alpine3.12 as node-builder
WORKDIR /authorizer WORKDIR /authorizer
COPY app app COPY app app
COPY dashboard dashboard
COPY Makefile . COPY Makefile .
RUN apk add build-base &&\ RUN apk add build-base &&\
make build-app make build-app && \
make build-dashboard
FROM alpine:latest FROM alpine:latest
WORKDIR /root/ WORKDIR /root/
RUN mkdir app RUN mkdir app dashboard
COPY --from=node-builder /authorizer/app/build app/build COPY --from=node-builder /authorizer/app/build app/build
COPY --from=node-builder /authorizer/dashboard/build dashboard/build
COPY --from=go-builder /authorizer/build build COPY --from=go-builder /authorizer/build build
COPY templates templates COPY templates templates
EXPOSE 8080 EXPOSE 8080

View File

@@ -5,7 +5,9 @@ cmd:
cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server' cd server && go build -ldflags "-w -X main.VERSION=$(VERSION)" -o '../build/server'
build-app: build-app:
cd app && npm i && npm run build cd app && npm i && npm run build
build-dashboard:
cd dashboard && npm i && npm run build
clean: clean:
rm -rf build rm -rf build
test: test:
cd server && go clean --testcache && go test -v ./__test__ cd server && go clean --testcache && go test -v ./test

11
TODO.md
View File

@@ -1,5 +1,16 @@
# Task List # Task List
## Implement better way of handling jwt tokens
Check: https://hasura.io/blog/best-practices-of-using-jwt-with-graphql/#server-side-rendering-ssr
- [x] Set finger print in response cookie (https://github.com/hasura/jwt-guide/blob/60a7a86146d604fc48a799fffdee712be1c52cd0/lib/setFingerprintCookieAndSignJwt.ts#L8)
- [x] Save refresh token in session store
- [x] refresh token should be made more secure with the help of secure token rotation. Every time new token is requested new refresh token should be generated
- [x] Return jwt in response
- [x] To get session send finger print and refresh token [if they are valid -> a new access token is generated and sent to user]
- [x] Refresh token should be long living token (refresh token + finger print hash should be verified)
## Open ID compatible claims and schema ## Open ID compatible claims and schema
- [x] Rename `schema.graphqls` and re generate schema - [x] Rename `schema.graphqls` and re generate schema

View File

@@ -1,3 +1,14 @@
# Authorizer APP # Authorizer APP
App that can be used as login wall for your any application in combination with @authorizerdev/@authorizer.js App that can be used as login wall for your any application in combination with @authorizerdev/@authorizer.js
### Getting started
**Setting up locally**
- `cd app`
- `npm start`
**Creating production build**
- `make build-app`

58
app/package-lock.json generated
View File

@@ -5,19 +5,19 @@
"requires": true, "requires": true,
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-js": { "@authorizerdev/authorizer-js": {
"version": "0.2.0", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.0.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz",
"integrity": "sha512-T2gurEYEP1T56j9IwDIWP1PsELoWcU7TBl5G0UsMqFQhKo7T6p2hM634Z7PS1yLegU3aihuvHGI0C0n4xSo0VQ==", "integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==",
"requires": { "requires": {
"node-fetch": "^2.6.1" "node-fetch": "^2.6.1"
} }
}, },
"@authorizerdev/authorizer-react": { "@authorizerdev/authorizer-react": {
"version": "0.4.0", "version": "0.4.3",
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.0.tgz", "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz",
"integrity": "sha512-ydE7xrP3cqeTtU923bK0+OIB1fnL0VHnbThcNa41n89XUPV3VBhZ23gxMg90nqon8JFE5g2TNz7+/qBCOQ7aZw==", "integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==",
"requires": { "requires": {
"@authorizerdev/authorizer-js": "^0.2.0", "@authorizerdev/authorizer-js": "^0.2.1",
"final-form": "^4.20.2", "final-form": "^4.20.2",
"react-final-form": "^6.5.3", "react-final-form": "^6.5.3",
"styled-components": "^5.3.0" "styled-components": "^5.3.0"
@@ -32,11 +32,11 @@
} }
}, },
"@babel/generator": { "@babel/generator": {
"version": "7.16.7", "version": "7.16.8",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz",
"integrity": "sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg==", "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==",
"requires": { "requires": {
"@babel/types": "^7.16.7", "@babel/types": "^7.16.8",
"jsesc": "^2.5.1", "jsesc": "^2.5.1",
"source-map": "^0.5.0" "source-map": "^0.5.0"
} }
@@ -105,9 +105,9 @@
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
}, },
"@babel/highlight": { "@babel/highlight": {
"version": "7.16.7", "version": "7.16.10",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz",
"integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==",
"requires": { "requires": {
"@babel/helper-validator-identifier": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7",
"chalk": "^2.0.0", "chalk": "^2.0.0",
@@ -115,9 +115,9 @@
} }
}, },
"@babel/parser": { "@babel/parser": {
"version": "7.16.7", "version": "7.16.12",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz",
"integrity": "sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA==" "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A=="
}, },
"@babel/runtime": { "@babel/runtime": {
"version": "7.14.8", "version": "7.14.8",
@@ -138,26 +138,26 @@
} }
}, },
"@babel/traverse": { "@babel/traverse": {
"version": "7.16.7", "version": "7.16.10",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz",
"integrity": "sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ==", "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==",
"requires": { "requires": {
"@babel/code-frame": "^7.16.7", "@babel/code-frame": "^7.16.7",
"@babel/generator": "^7.16.7", "@babel/generator": "^7.16.8",
"@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-environment-visitor": "^7.16.7",
"@babel/helper-function-name": "^7.16.7", "@babel/helper-function-name": "^7.16.7",
"@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7",
"@babel/helper-split-export-declaration": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7",
"@babel/parser": "^7.16.7", "@babel/parser": "^7.16.10",
"@babel/types": "^7.16.7", "@babel/types": "^7.16.8",
"debug": "^4.1.0", "debug": "^4.1.0",
"globals": "^11.1.0" "globals": "^11.1.0"
} }
}, },
"@babel/types": { "@babel/types": {
"version": "7.16.7", "version": "7.16.8",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz",
"integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==", "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==",
"requires": { "requires": {
"@babel/helper-validator-identifier": "^7.16.7", "@babel/helper-validator-identifier": "^7.16.7",
"to-fast-properties": "^2.0.0" "to-fast-properties": "^2.0.0"
@@ -420,9 +420,9 @@
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}, },
"node-fetch": { "node-fetch": {
"version": "2.6.6", "version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
"integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
"requires": { "requires": {
"whatwg-url": "^5.0.0" "whatwg-url": "^5.0.0"
} }

View File

@@ -42,7 +42,7 @@ export default function App() {
<BrowserRouter> <BrowserRouter>
<AuthorizerProvider <AuthorizerProvider
config={{ config={{
authorizerURL: globalState.authorizerURL, authorizerURL: window.location.origin,
redirectURL: globalState.redirectURL, redirectURL: globalState.redirectURL,
}} }}
> >

12
dashboard/README.md Normal file
View File

@@ -0,0 +1,12 @@
# Authorizer dashboard
### Getting started
**Setting up locally**
- `cd dashboard`
- `npm start`
**Creating production build**
- `make build-dashboard`

View 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

File diff suppressed because it is too large Load Diff

30
dashboard/package.json Normal file
View 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"
}
}

45
dashboard/src/App.tsx Normal file
View File

@@ -0,0 +1,45 @@
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 { AuthContextProvider } from './contexts/AuthContext';
const queryClient = createClient({
url: '/graphql',
fetchOptions: () => {
return {
credentials: 'include',
};
},
});
const theme = extendTheme({
styles: {
global: {
'html, body, #root': {
fontFamily: 'Avenir, Helvetica, Arial, sans-serif',
height: '100%',
},
},
},
colors: {
blue: {
500: 'rgb(59,130,246)',
},
},
});
export default function App() {
return (
<ChakraProvider theme={theme}>
<Provider value={queryClient}>
<BrowserRouter basename="/dashboard">
<AuthContextProvider>
<AppRoutes />
</AuthContextProvider>
</BrowserRouter>
</Provider>
</ChakraProvider>
);
}

0
dashboard/src/Router.tsx Normal file
View File

View File

@@ -0,0 +1,215 @@
import React, { ReactNode } from 'react';
import {
IconButton,
Avatar,
Box,
CloseButton,
Flex,
Image,
HStack,
VStack,
Icon,
useColorModeValue,
Link,
Text,
BoxProps,
FlexProps,
Menu,
MenuButton,
MenuItem,
MenuList,
} from '@chakra-ui/react';
import {
FiHome,
FiTrendingUp,
FiCompass,
FiStar,
FiSettings,
FiMenu,
FiUser,
FiUsers,
FiChevronDown,
} from 'react-icons/fi';
import { IconType } from 'react-icons';
import { ReactText } from 'react';
import { useMutation } from 'urql';
import { NavLink, useNavigate, useLocation } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthContext';
import { AdminLogout } from '../graphql/mutation';
interface LinkItemProps {
name: string;
icon: IconType;
route: string;
}
const LinkItems: Array<LinkItemProps> = [
{ name: 'Home', icon: FiHome, route: '/' },
{ name: 'Users', icon: FiUsers, route: '/users' },
{ name: 'Environment Variables', icon: FiSettings, route: '/environment' },
];
interface SidebarProps extends BoxProps {
onClose: () => void;
}
export const Sidebar = ({ onClose, ...rest }: SidebarProps) => {
const { pathname } = useLocation();
return (
<Box
transition="3s ease"
bg={useColorModeValue('white', 'gray.900')}
borderRight="1px"
borderRightColor={useColorModeValue('gray.200', 'gray.700')}
w={{ base: 'full', md: 60 }}
pos="fixed"
h="full"
{...rest}
>
<Flex h="20" alignItems="center" mx="8" justifyContent="space-between">
<NavLink to="/">
<Flex alignItems="center">
<Image
src="https://authorizer.dev/images/logo.png"
alt="logo"
height="36px"
/>
<Text fontSize="large" ml="2" letterSpacing="3">
AUTHORIZER
</Text>
</Flex>
</NavLink>
<CloseButton display={{ base: 'flex', md: 'none' }} onClick={onClose} />
</Flex>
{LinkItems.map((link) => (
<NavLink key={link.name} to={link.route}>
<NavItem
icon={link.icon}
color={pathname === link.route ? 'blue.500' : ''}
>
{link.name}
</NavItem>
</NavLink>
))}
</Box>
);
};
interface NavItemProps extends FlexProps {
icon: IconType;
children: ReactText;
}
export const NavItem = ({ icon, children, ...rest }: NavItemProps) => {
return (
<Link
href="#"
style={{ textDecoration: 'none' }}
_focus={{ boxShadow: 'none' }}
>
<Flex
align="center"
p="3"
mx="3"
borderRadius="md"
role="group"
cursor="pointer"
_hover={{
bg: 'blue.500',
color: 'white',
}}
{...rest}
>
{icon && (
<Icon
mr="4"
fontSize="16"
_groupHover={{
color: 'white',
}}
as={icon}
/>
)}
{children}
</Flex>
</Link>
);
};
interface MobileProps extends FlexProps {
onOpen: () => void;
}
export const MobileNav = ({ onOpen, ...rest }: MobileProps) => {
const [_, logout] = useMutation(AdminLogout);
const { setIsLoggedIn } = useAuthContext();
const navigate = useNavigate();
const handleLogout = async () => {
await logout();
setIsLoggedIn(false);
navigate('/', { replace: true });
};
return (
<Flex
ml={{ base: 0, md: 60 }}
px={{ base: 4, md: 4 }}
height="20"
position="fixed"
right="0"
left="0"
alignItems="center"
bg={useColorModeValue('white', 'gray.900')}
borderBottomWidth="1px"
borderBottomColor={useColorModeValue('gray.200', 'gray.700')}
justifyContent={{ base: 'space-between', md: 'flex-end' }}
{...rest}
>
<IconButton
display={{ base: 'flex', md: 'none' }}
onClick={onOpen}
variant="outline"
aria-label="open menu"
icon={<FiMenu />}
/>
<Image
src="https://authorizer.dev/images/logo.png"
alt="logo"
height="36px"
display={{ base: 'flex', md: 'none' }}
/>
<HStack spacing={{ base: '0', md: '6' }}>
<Flex alignItems={'center'}>
<Menu>
<MenuButton
py={2}
transition="all 0.3s"
_focus={{ boxShadow: 'none' }}
>
<HStack>
<FiUser />
<VStack
display={{ base: 'none', md: 'flex' }}
alignItems="flex-start"
spacing="1px"
ml="2"
>
<Text fontSize="sm">Admin</Text>
</VStack>
<Box display={{ base: 'none', md: 'flex' }}>
<FiChevronDown />
</Box>
</HStack>
</MenuButton>
<MenuList
bg={useColorModeValue('white', 'gray.900')}
borderColor={useColorModeValue('gray.200', 'gray.700')}
>
<MenuItem onClick={handleLogout}>Sign out</MenuItem>
</MenuList>
</Menu>
</Flex>
</HStack>
</Flex>
);
};

View File

@@ -0,0 +1 @@
export const LOGO_URL = "https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png"

View File

@@ -0,0 +1,48 @@
import React, { createContext, useState, useContext, useEffect } from 'react';
import { Center, Spinner } from '@chakra-ui/react';
import { useQuery } from 'urql';
import { useLocation, useNavigate } from 'react-router-dom';
import { AdminSessionQuery } from '../graphql/queries';
import { hasAdminSecret } from '../utils';
const AuthContext = createContext({
isLoggedIn: false,
setIsLoggedIn: (data: boolean) => {},
});
export const AuthContextProvider = ({ children }: { children: any }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
const { pathname } = useLocation();
const navigate = useNavigate();
const [{ fetching, data, error }] = useQuery({
query: AdminSessionQuery,
});
useEffect(() => {
if (!fetching && !error) {
setIsLoggedIn(true);
if (pathname === '/login' || pathname === 'signup') {
navigate('/', { replace: true });
}
}
}, [fetching, error]);
if (fetching) {
return (
<Center>
<Spinner />
</Center>
);
}
return (
<AuthContext.Provider value={{ isLoggedIn, setIsLoggedIn }}>
{children}
</AuthContext.Provider>
);
};
export const useAuthContext = () => useContext(AuthContext);

View File

@@ -0,0 +1,23 @@
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
}
}
`;
export const AdminLogout = `
mutation adminLogout {
_admin_logout {
message
}
}
`;

View File

@@ -0,0 +1,7 @@
export const AdminSessionQuery = `
query {
_admin_session{
message
}
}
`;

5
dashboard/src/index.tsx Normal file
View File

@@ -0,0 +1,5 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));

View File

@@ -0,0 +1,31 @@
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%"
bg="gray.100"
alignItems="center"
justifyContent="center"
flexDirection="column"
>
<Flex alignItems="center">
<Image
src="https://authorizer.dev/images/logo.png"
alt="logo"
height="50"
/>
<Text fontSize="x-large" ml="3" letterSpacing="3">
AUTHORIZER
</Text>
</Flex>
<Box p="6" m="5" rounded="5" bg="white" w="500px" shadow="xl">
{children}
</Box>
</Flex>
);
}

View File

@@ -0,0 +1,39 @@
import {
Box,
Drawer,
DrawerContent,
useDisclosure,
useColorModeValue,
} from '@chakra-ui/react';
import React, { ReactNode } from 'react';
import { Sidebar, MobileNav } from '../components/Menu';
export function DashboardLayout({ children }: { children: ReactNode }) {
const { isOpen, onOpen, onClose } = useDisclosure();
return (
<Box minH="100vh" bg={useColorModeValue('gray.100', 'gray.900')}>
<Sidebar
onClose={() => onClose}
display={{ base: 'none', md: 'block' }}
/>
<Drawer
autoFocus={false}
isOpen={isOpen}
placement="left"
onClose={onClose}
returnFocusOnClose={false}
onOverlayClick={onClose}
size="full"
>
<DrawerContent>
<Sidebar onClose={onClose} />
</DrawerContent>
</Drawer>
{/* mobilenav */}
<MobileNav onOpen={onOpen} />
<Box ml={{ base: 0, md: 60 }} p="4" pt="24">
{children}
</Box>
</Box>
);
}

View File

@@ -0,0 +1,124 @@
import {
Button,
FormControl,
FormLabel,
Input,
useToast,
VStack,
Text,
Divider,
} 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 { useNavigate } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthContext';
import { capitalizeFirstLetter, hasAdminSecret } from '../utils';
export default function Auth() {
const [loginResult, login] = useMutation(AdminLogin);
const [signUpResult, signup] = useMutation(AdminSignup);
const { setIsLoggedIn } = useAuthContext();
const toast = useToast();
const navigate = useNavigate();
const isLogin = hasAdminSecret();
const handleSubmit = (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.data) {
setIsLoggedIn(true);
navigate('/', { replace: true });
}
});
};
const errors = isLogin ? loginResult.error : signUpResult.error;
useEffect(() => {
if (errors?.graphQLErrors) {
(errors?.graphQLErrors || []).map((error: any) => {
toast({
title: capitalizeFirstLetter(error.message),
isClosable: true,
status: 'error',
position: 'bottom-right',
});
});
}
}, [errors]);
return (
<AuthLayout>
<Text
fontSize="large"
textAlign="center"
color="gray.600"
fontWeight="bold"
mb="2"
>
Hi there 👋 <br />
</Text>
<Text fontSize="large" textAlign="center" color="gray.500" mb="8">
Welcome to Authorizer Administrative Dashboard
</Text>
<form onSubmit={handleSubmit}>
<VStack spacing="5" justify="space-between">
<FormControl isRequired>
{/* <FormLabel htmlFor="admin-secret">
{isLogin ? 'Enter' : 'Configure'} Admin Secret
</FormLabel> */}
<Input
size="lg"
id="admin-secret"
placeholder="Admin secret"
type="password"
minLength={!isLogin ? 6 : 1}
/>
</FormControl>
<Button
isLoading={signUpResult.fetching || loginResult.fetching}
colorScheme="blue"
size="lg"
w="100%"
d="block"
type="submit"
>
{isLogin ? 'Login' : 'Sign up'}
</Button>
{isLogin ? (
<Text color="gray.600" fontSize="sm">
<b>Note:</b> In case if you have forgot your admin secret, you can
reset it by updating <code>ADMIN_SECRET</code> environment
variable. For more information, please refer to the{' '}
<a href="https://docs.authorizer.dev/core/env/">documentation</a>.
</Text>
) : (
<Text color="gray.600" fontSize="sm">
<b>Note:</b> You can also configure admin secret by setting{' '}
<code>ADMIN_SECRET</code> environment variable. For more
information, please refer to the{' '}
<a href="https://docs.authorizer.dev/core/env/">documentation</a>.
</Text>
)}
</VStack>
</form>
</AuthLayout>
);
}

View File

@@ -0,0 +1,35 @@
import { Box, Divider, Flex } from '@chakra-ui/react';
import React from 'react';
// Don't allow changing database from here as it can cause persistence issues
export default function Environment() {
return (
<Box m="5" p="5" bg="white" rounded="md">
<h1>Social Media Logins</h1>
<Divider />- Add horizontal input for clientID and secret for - Google -
Github - Facebook
<h1>Roles</h1>
<Divider />- Add tagged input for roles, default roles, and protected
roles
<h1>JWT Configurations</h1>
<Divider />- Add input for JWT Type (keep this disabled for now with
notice saying, "More JWT types will be enabled in upcoming releases"),JWT
secret, JWT role claim
<h1>Session Storage</h1>
<Divider />- Add input for redis url
<h1>Email Configurations</h1>
<Divider />- Add input for SMTP Host, PORT, Username, Password, From
Email,
<h1>White Listing</h1>
<Divider />- Add input for allowed origins
<h1>Organization Information</h1>
<Divider />- Add input for organization name, and logo
<h1>Custom Scripts</h1>
<Divider />- For now add text area input for CUSTOM_ACCESS_TOKEN_SCRIPT
<h1>Disable Features</h1>
<Divider />
<h1>Danger</h1>
<Divider />- Include changing admin secret
</Box>
);
}

View File

@@ -0,0 +1,18 @@
import { Text } from '@chakra-ui/react';
import React from 'react';
export default function Home() {
return (
<>
<Text fontSize="2xl" fontWeight="bold">
Hi there 👋 <br />
</Text>
<Text fontSize="xl" color="gray.700">
Welcome to Authorizer Administrative Dashboard! <br />
Please use this dashboard to configure your environment variables or
have look at your users
</Text>
</>
);
}

View File

@@ -0,0 +1,6 @@
import { Box } from '@chakra-ui/react';
import React from 'react';
export default function Users() {
return <Box>Welcome to Users Page</Box>;
}

View File

@@ -0,0 +1,41 @@
import React, { lazy, Suspense } from 'react';
import { Outlet, Route, Routes } from 'react-router-dom';
import { useAuthContext } from '../contexts/AuthContext';
import { DashboardLayout } from '../layouts/DashboardLayout';
const Auth = lazy(() => import('../pages/Auth'));
const Environment = lazy(() => import('../pages/Environment'));
const Home = lazy(() => import('../pages/Home'));
const Users = lazy(() => import('../pages/Users'));
export const AppRoutes = () => {
const { isLoggedIn } = useAuthContext();
if (isLoggedIn) {
return (
<Suspense fallback={<></>}>
<Routes>
<Route
element={
<DashboardLayout>
<Outlet />
</DashboardLayout>
}
>
<Route path="/" element={<Home />} />
<Route path="users" element={<Users />} />
<Route path="environment" element={<Environment />} />
</Route>
</Routes>
</Suspense>
);
}
return (
<Suspense fallback={<></>}>
<Routes>
<Route path="/" element={<Auth />} />
</Routes>
</Suspense>
);
};

View File

@@ -0,0 +1,6 @@
export const hasAdminSecret = () => {
return (<any>window)['__authorizer__'].isOnboardingCompleted === true;
};
export const capitalizeFirstLetter = (data: string): string =>
data.charAt(0).toUpperCase() + data.slice(1);

72
dashboard/tsconfig.json Normal file
View 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. */
}
}

View File

@@ -1,7 +1,7 @@
VERSION="$1" VERSION="$1"
make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make
FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz
tar cvfz ${FILE_NAME} .env app/build build templates tar cvfz ${FILE_NAME} .env app/build build templates dashboard/build
AUTH="Authorization: token $GITHUB_TOKEN" AUTH="Authorization: token $GITHUB_TOKEN"
RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION}) RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION})
echo $RELASE_INFO echo $RELASE_INFO

View File

@@ -1,34 +0,0 @@
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 deleteUserTest(s TestSetup, t *testing.T) {
t.Run(`should delete users with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "delete_user." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.DeleteUser(ctx, model.DeleteUserInput{
Email: email,
})
assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
_, err = resolvers.DeleteUser(ctx, model.DeleteUserInput{
Email: email,
})
assert.Nil(t, err)
cleanData(email)
})
}

View File

@@ -1,25 +0,0 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/stretchr/testify/assert"
)
func TestEnvs(t *testing.T) {
constants.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{"*"})
}

View File

@@ -1,35 +0,0 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func logoutTests(s TestSetup, t *testing.T) {
t.Run(`should logout user`, func(t *testing.T) {
req, ctx := createContext(s)
email := "logout." + s.TestInfo.Email
_, err := resolvers.MagicLinkLogin(ctx, model.MagicLinkLoginInput{
Email: email,
})
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.MagicLinkLogin.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
_, err = resolvers.Logout(ctx)
assert.Nil(t, err)
_, err = resolvers.Profile(ctx)
assert.NotNil(t, err, "unauthorized")
cleanData(email)
})
}

View File

@@ -1,35 +0,0 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func magicLinkLoginTests(s TestSetup, t *testing.T) {
t.Run(`should login with magic link`, func(t *testing.T) {
req, ctx := createContext(s)
email := "magic_link_login." + s.TestInfo.Email
_, err := resolvers.MagicLinkLogin(ctx, model.MagicLinkLoginInput{
Email: email,
})
assert.Nil(t, err)
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.MagicLinkLogin.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
_, err = resolvers.Profile(ctx)
assert.Nil(t, err)
cleanData(email)
})
}

View File

@@ -1,47 +0,0 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
)
func TestResolvers(t *testing.T) {
databases := map[string]string{
enum.Sqlite.String(): "../../data.db",
enum.Arangodb.String(): "http://root:root@localhost:8529",
enum.Mongodb.String(): "mongodb://localhost:27017",
}
for dbType, dbURL := range databases {
constants.DATABASE_URL = dbURL
constants.DATABASE_TYPE = dbType
db.InitDB()
s := testSetup()
defer s.Server.Close()
t.Run("should pass tests for "+dbType, func(t *testing.T) {
loginTests(s, t)
signupTests(s, t)
forgotPasswordTest(s, t)
resendVerifyEmailTests(s, t)
resetPasswordTest(s, t)
verifyEmailTest(s, t)
sessionTests(s, t)
profileTests(s, t)
updateProfileTests(s, t)
magicLinkLoginTests(s, t)
logoutTests(s, t)
metaTests(s, t)
// admin tests
verificationRequestsTest(s, t)
usersTest(s, t)
deleteUserTest(s, t)
updateUserTest(s, t)
})
}
}

View File

@@ -1,42 +0,0 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func sessionTests(s TestSetup, t *testing.T) {
t.Run(`should allow access to profile with session only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "session." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.Session(ctx, []string{})
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
sessionRes, err := resolvers.Session(ctx, []string{})
assert.Nil(t, err)
newToken := *sessionRes.AccessToken
assert.Equal(t, token, newToken, "tokens should be equal")
cleanData(email)
})
}

View File

@@ -1,35 +0,0 @@
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 verificationRequestsTest(s TestSetup, t *testing.T) {
t.Run(`should get verification requests with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "verification_requests." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
requests, err := resolvers.VerificationRequests(ctx)
assert.NotNil(t, err, "unauthorizer")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
requests, err = resolvers.VerificationRequests(ctx)
assert.Nil(t, err)
rLen := len(requests)
assert.GreaterOrEqual(t, rLen, 1)
cleanData(email)
})
}

View File

@@ -1,51 +0,0 @@
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
// ROLES
ROLES = []string{}
PROTECTED_ROLES = []string{}
DEFAULT_ROLES = []string{}
JWT_ROLE_CLAIM = "role"
// 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 = ""
// Org envs
ORGANIZATION_NAME = "Authorizer"
ORGANIZATION_LOGO = "https://authorizer.dev/images/logo.png"
)

View File

@@ -0,0 +1,18 @@
package constants
const (
// DbTypePostgres is the postgres database type
DbTypePostgres = "postgres"
// DbTypeSqlite is the sqlite database type
DbTypeSqlite = "sqlite"
// DbTypeMysql is the mysql database type
DbTypeMysql = "mysql"
// DbTypeSqlserver is the sqlserver database type
DbTypeSqlserver = "sqlserver"
// DbTypeArangodb is the arangodb database type
DbTypeArangodb = "arangodb"
// DbTypeMongodb is the mongodb database type
DbTypeMongodb = "mongodb"
// DbTypeFaunadb is the faunadb database type
DbTypeFaunadb = "faunadb"
)

95
server/constants/env.go Normal file
View File

@@ -0,0 +1,95 @@
package constants
const (
// Envstore identifier
// StringStore string store identifier
StringStoreIdentifier = "stringStore"
// BoolStore bool store identifier
BoolStoreIdentifier = "boolStore"
// SliceStore slice store identifier
SliceStoreIdentifier = "sliceStore"
// EnvKeyEnv key for env variable ENV
EnvKeyEnv = "ENV"
// EnvKeyEnvPath key for cli arg variable ENV_PATH
EnvKeyEnvPath = "ENV_PATH"
// EnvKeyVersion key for build arg version
EnvKeyVersion = "VERSION"
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
// EnvKeyPort key for env variable PORT
EnvKeyPort = "PORT"
// EnvKeyAdminSecret key for env variable ADMIN_SECRET
EnvKeyAdminSecret = "ADMIN_SECRET"
// EnvKeyDatabaseType key for env variable DATABASE_TYPE
EnvKeyDatabaseType = "DATABASE_TYPE"
// EnvKeyDatabaseURL key for env variable DATABASE_URL
EnvKeyDatabaseURL = "DATABASE_URL"
// EnvKeyDatabaseName key for env variable DATABASE_NAME
EnvKeyDatabaseName = "DATABASE_NAME"
// EnvKeySmtpHost key for env variable SMTP_HOST
EnvKeySmtpHost = "SMTP_HOST"
// EnvKeySmtpPort key for env variable SMTP_PORT
EnvKeySmtpPort = "SMTP_PORT"
// EnvKeySmtpUsername key for env variable SMTP_USERNAME
EnvKeySmtpUsername = "SMTP_USERNAME"
// EnvKeySmtpPassword key for env variable SMTP_PASSWORD
EnvKeySmtpPassword = "SMTP_PASSWORD"
// EnvKeySenderEmail key for env variable SENDER_EMAIL
EnvKeySenderEmail = "SENDER_EMAIL"
// EnvKeyJwtType key for env variable JWT_TYPE
EnvKeyJwtType = "JWT_TYPE"
// EnvKeyJwtSecret key for env variable JWT_SECRET
EnvKeyJwtSecret = "JWT_SECRET"
// EnvKeyAllowedOrigins key for env variable ALLOWED_ORIGINS
EnvKeyAllowedOrigins = "ALLOWED_ORIGINS"
// EnvKeyAppURL key for env variable APP_URL
EnvKeyAppURL = "APP_URL"
// EnvKeyRedisURL key for env variable REDIS_URL
EnvKeyRedisURL = "REDIS_URL"
// EnvKeyCookieName key for env variable COOKIE_NAME
EnvKeyCookieName = "COOKIE_NAME"
// EnvKeyAdminCookieName key for env variable ADMIN_COOKIE_NAME
EnvKeyAdminCookieName = "ADMIN_COOKIE_NAME"
// EnvKeyResetPasswordURL key for env variable RESET_PASSWORD_URL
EnvKeyResetPasswordURL = "RESET_PASSWORD_URL"
// EnvKeyEncryptionKey key for env variable ENCRYPTION_KEY
EnvKeyEncryptionKey = "ENCRYPTION_KEY"
// EnvKeyDisableEmailVerification key for env variable DISABLE_EMAIL_VERIFICATION
EnvKeyDisableEmailVerification = "DISABLE_EMAIL_VERIFICATION"
// EnvKeyDisableBasicAuthentication key for env variable DISABLE_BASIC_AUTH
EnvKeyDisableBasicAuthentication = "DISABLE_BASIC_AUTHENTICATION"
// EnvKeyDisableMagicLinkLogin key for env variable DISABLE_MAGIC_LINK_LOGIN
EnvKeyDisableMagicLinkLogin = "DISABLE_MAGIC_LINK_LOGIN"
// EnvKeyDisableLoginPage key for env variable DISABLE_LOGIN_PAGE
EnvKeyDisableLoginPage = "DISABLE_LOGIN_PAGE"
// EnvKeyRoles key for env variable ROLES
EnvKeyRoles = "ROLES"
// EnvKeyProtectedRoles key for env variable PROTECTED_ROLES
EnvKeyProtectedRoles = "PROTECTED_ROLES"
// EnvKeyDefaultRoles key for env variable DEFAULT_ROLES
EnvKeyDefaultRoles = "DEFAULT_ROLES"
// EnvKeyJwtRoleClaim key for env variable JWT_ROLE_CLAIM
EnvKeyJwtRoleClaim = "JWT_ROLE_CLAIM"
// EnvKeyGoogleClientID key for env variable GOOGLE_CLIENT_ID
EnvKeyGoogleClientID = "GOOGLE_CLIENT_ID"
// EnvKeyGoogleClientSecret key for env variable GOOGLE_CLIENT_SECRET
EnvKeyGoogleClientSecret = "GOOGLE_CLIENT_SECRET"
// EnvKeyGithubClientID key for env variable GITHUB_CLIENT_ID
EnvKeyGithubClientID = "GITHUB_CLIENT_ID"
// EnvKeyGithubClientSecret key for env variable GITHUB_CLIENT_SECRET
EnvKeyGithubClientSecret = "GITHUB_CLIENT_SECRET"
// EnvKeyFacebookClientID key for env variable FACEBOOK_CLIENT_ID
EnvKeyFacebookClientID = "FACEBOOK_CLIENT_ID"
// EnvKeyFacebookClientSecret key for env variable FACEBOOK_CLIENT_SECRET
EnvKeyFacebookClientSecret = "FACEBOOK_CLIENT_SECRET"
// EnvKeyOrganizationName key for env variable ORGANIZATION_NAME
EnvKeyOrganizationName = "ORGANIZATION_NAME"
// EnvKeyOrganizationLogo key for env variable ORGANIZATION_LOGO
EnvKeyOrganizationLogo = "ORGANIZATION_LOGO"
// EnvKeyIsProd key for env variable IS_PROD
EnvKeyIsProd = "IS_PROD"
// EnvKeyCustomAccessTokenScript key for env variable CUSTOM_ACCESS_TOKEN_SCRIPT
EnvKeyCustomAccessTokenScript = "CUSTOM_ACCESS_TOKEN_SCRIPT"
)

View File

@@ -1,6 +1,6 @@
package constants package constants
var ( const (
// Ref: https://github.com/qor/auth/blob/master/providers/google/google.go // Ref: https://github.com/qor/auth/blob/master/providers/google/google.go
// deprecated and not used. instead we follow open id approach for google login // deprecated and not used. instead we follow open id approach for google login
GoogleUserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo" GoogleUserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo"

View File

@@ -0,0 +1,14 @@
package constants
const (
// SignupMethodBasicAuth is the basic_auth signup method
SignupMethodBasicAuth = "basic_auth"
// SignupMethodMagicLinkLogin is the magic_link_login signup method
SignupMethodMagicLinkLogin = "magic_link_login"
// SignupMethodGoogle is the google signup method
SignupMethodGoogle = "google"
// SignupMethodGithub is the github signup method
SignupMethodGithub = "github"
// SignupMethodFacebook is the facebook signup method
SignupMethodFacebook = "facebook"
)

View File

@@ -0,0 +1,8 @@
package constants
const (
// TokenTypeRefreshToken is the refresh_token token type
TokenTypeRefreshToken = "refresh_token"
// TokenTypeAccessToken is the access_token token type
TokenTypeAccessToken = "access_token"
)

View File

@@ -0,0 +1,12 @@
package constants
const (
// VerificationTypeBasicAuthSignup is the basic_auth_signup verification type
VerificationTypeBasicAuthSignup = "basic_auth_signup"
// VerificationTypeMagicLinkLogin is the magic_link_login verification type
VerificationTypeMagicLinkLogin = "magic_link_login"
// VerificationTypeUpdateEmail is the update_email verification type
VerificationTypeUpdateEmail = "update_email"
// VerificationTypeForgotPassword is the forgot_password verification type
VerificationTypeForgotPassword = "forgot_password"
)

View File

@@ -0,0 +1,44 @@
package cookie
import (
"net/url"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
)
// SetAdminCookie sets the admin cookie in the response
func SetAdminCookie(gc *gin.Context, token string) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
}
// GetAdminCookie gets the admin cookie from the request
func GetAdminCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName))
if err != nil {
return "", err
}
// cookie escapes special characters like $
// hence we need to unescape before comparing
decodedValue, err := url.QueryUnescape(cookie.Value)
if err != nil {
return "", err
}
return decodedValue, nil
}
// DeleteAdminCookie sets the response cookie to empty
func DeleteAdminCookie(gc *gin.Context) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
}

101
server/cookie/cookie.go Normal file
View File

@@ -0,0 +1,101 @@
package cookie
import (
"net/http"
"net/url"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin"
)
// SetCookie sets the cookie in the response. It sets 4 cookies
// 1 COOKIE_NAME.access_token jwt token for the host (temp.abc.com)
// 2 COOKIE_NAME.access_token.domain jwt token for the domain (abc.com).
// 3 COOKIE_NAME.fingerprint fingerprint hash for the refresh token verification.
// 4 COOKIE_NAME.refresh_token refresh token
// Note all sites don't allow 2nd type of cookie
func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
domain := utils.GetDomainName(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
if domain != "localhost" {
domain = "." + domain
}
year := 60 * 60 * 24 * 365
thirtyMin := 60 * 30
gc.SetSameSite(http.SameSiteNoneMode)
// set cookie for host
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", accessToken, thirtyMin, "/", host, secure, httpOnly)
// in case of subdomain, set cookie for domain
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", accessToken, thirtyMin, "/", domain, secure, httpOnly)
// set finger print cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", fingerprintHash, year, "/", host, secure, httpOnly)
// set refresh token cookie (this should be accessed via cookie only)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", refreshToken, year, "/", host, secure, httpOnly)
}
// GetAccessTokenCookie to get access token cookie from the request
func GetAccessTokenCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token")
if err != nil {
cookie, err = gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".access_token.domain")
if err != nil {
return "", err
}
}
return cookie.Value, nil
}
// GetRefreshTokenCookie to get refresh token cookie
func GetRefreshTokenCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".refresh_token")
if err != nil {
return "", err
}
return cookie.Value, nil
}
// GetFingerPrintCookie to get fingerprint cookie
func GetFingerPrintCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName) + ".fingerprint")
if err != nil {
return "", err
}
// cookie escapes special characters like $
// hence we need to unescape before comparing
decodedValue, err := url.QueryUnescape(cookie.Value)
if err != nil {
return "", err
}
return decodedValue, nil
}
// DeleteCookie sets response cookies to expire
func DeleteCookie(gc *gin.Context) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
domain := utils.GetDomainName(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".access_token.domain", "", -1, "/", domain, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".fingerprint", "", -1, "/", host, secure, httpOnly)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyCookieName)+".refresh_token", "", -1, "/", host, secure, httpOnly)
}

View File

@@ -1,104 +0,0 @@
package db
import (
"context"
"log"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/http"
"github.com/authorizerdev/authorizer/server/constants"
)
// for this we need arangodb instance up and running
// for local testing we can use dockerized version of it
// docker run -p 8529:8529 -e ARANGO_ROOT_PASSWORD=root arangodb/arangodb:3.8.4
func initArangodb() (arangoDriver.Database, error) {
ctx := context.Background()
conn, err := http.NewConnection(http.ConnectionConfig{
Endpoints: []string{constants.DATABASE_URL},
})
if err != nil {
return nil, err
}
arangoClient, err := arangoDriver.NewClient(arangoDriver.ClientConfig{
Connection: conn,
})
if err != nil {
return nil, err
}
var arangodb driver.Database
arangodb_exists, err := arangoClient.DatabaseExists(nil, constants.DATABASE_NAME)
if arangodb_exists {
log.Println(constants.DATABASE_NAME + " db exists already")
arangodb, err = arangoClient.Database(nil, constants.DATABASE_NAME)
if err != nil {
return nil, err
}
} else {
arangodb, err = arangoClient.CreateDatabase(nil, constants.DATABASE_NAME, nil)
if err != nil {
return nil, err
}
}
userCollectionExists, err := arangodb.CollectionExists(ctx, Collections.User)
if userCollectionExists {
log.Println(Collections.User + " collection exists already")
} else {
_, err = arangodb.CreateCollection(ctx, Collections.User, nil)
if err != nil {
log.Println("error creating collection("+Collections.User+"):", err)
}
}
userCollection, _ := arangodb.Collection(nil, Collections.User)
userCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
userCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
verificationRequestCollectionExists, err := arangodb.CollectionExists(ctx, Collections.VerificationRequest)
if verificationRequestCollectionExists {
log.Println(Collections.VerificationRequest + " collection exists already")
} else {
_, err = arangodb.CreateCollection(ctx, Collections.VerificationRequest, nil)
if err != nil {
log.Println("error creating collection("+Collections.VerificationRequest+"):", err)
}
}
verificationRequestCollection, _ := arangodb.Collection(nil, Collections.VerificationRequest)
verificationRequestCollection.EnsureHashIndex(ctx, []string{"email", "identifier"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
verificationRequestCollection.EnsureHashIndex(ctx, []string{"token"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true,
})
sessionCollectionExists, err := arangodb.CollectionExists(ctx, Collections.Session)
if sessionCollectionExists {
log.Println(Collections.Session + " collection exists already")
} else {
_, err = arangodb.CreateCollection(ctx, Collections.Session, nil)
if err != nil {
log.Println("error creating collection("+Collections.Session+"):", err)
}
}
sessionCollection, _ := arangodb.Collection(nil, Collections.Session)
sessionCollection.EnsureHashIndex(ctx, []string{"user_id"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true,
})
return arangodb, err
}

View File

@@ -3,127 +3,51 @@ package db
import ( import (
"log" "log"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/db/providers"
"go.mongodb.org/mongo-driver/mongo" "github.com/authorizerdev/authorizer/server/db/providers/arangodb"
"gorm.io/driver/mysql" "github.com/authorizerdev/authorizer/server/db/providers/faunadb"
"gorm.io/driver/postgres" "github.com/authorizerdev/authorizer/server/db/providers/mongodb"
"gorm.io/driver/sqlite" "github.com/authorizerdev/authorizer/server/db/providers/sql"
"gorm.io/driver/sqlserver" "github.com/authorizerdev/authorizer/server/envstore"
"gorm.io/gorm"
"gorm.io/gorm/schema"
) )
type Manager interface { // Provider returns the current database provider
AddUser(user User) (User, error) var Provider providers.Provider
UpdateUser(user User) (User, error)
DeleteUser(user User) error
GetUsers() ([]User, error)
GetUserByEmail(email string) (User, error)
GetUserByID(email string) (User, error)
AddVerification(verification VerificationRequest) (VerificationRequest, error)
GetVerificationByToken(token string) (VerificationRequest, error)
DeleteVerificationRequest(verificationRequest VerificationRequest) error
GetVerificationRequests() ([]VerificationRequest, error)
GetVerificationByEmail(email string, identifier string) (VerificationRequest, error)
AddSession(session Session) error
DeleteUserSession(userId string) error
}
type manager struct {
sqlDB *gorm.DB
arangodb arangoDriver.Database
mongodb *mongo.Database
}
// mainly used by nosql dbs
type CollectionList struct {
User string
VerificationRequest string
Session string
}
var (
IsORMSupported bool
IsArangoDB bool
IsMongoDB bool
Mgr Manager
Prefix = "authorizer_"
Collections = CollectionList{
User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions",
}
)
func InitDB() { func InitDB() {
var sqlDB *gorm.DB
var err error var err error
IsORMSupported = constants.DATABASE_TYPE != enum.Arangodb.String() && constants.DATABASE_TYPE != enum.Mongodb.String() isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeFaunadb
IsArangoDB = constants.DATABASE_TYPE == enum.Arangodb.String() isArangoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
IsMongoDB = constants.DATABASE_TYPE == enum.Mongodb.String() isMongoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
isFaunaDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeFaunadb
// sql db orm config if isSQL {
ormConfig := &gorm.Config{ Provider, err = sql.NewProvider()
NamingStrategy: schema.NamingStrategy{
TablePrefix: Prefix,
},
}
log.Println("db type:", constants.DATABASE_TYPE)
switch constants.DATABASE_TYPE {
case enum.Postgres.String():
sqlDB, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig)
break
case enum.Sqlite.String():
sqlDB, err = gorm.Open(sqlite.Open(constants.DATABASE_URL), ormConfig)
break
case enum.Mysql.String():
sqlDB, err = gorm.Open(mysql.Open(constants.DATABASE_URL), ormConfig)
break
case enum.SQLServer.String():
sqlDB, err = gorm.Open(sqlserver.Open(constants.DATABASE_URL), ormConfig)
break
case enum.Arangodb.String():
arangodb, err := initArangodb()
if err != nil { if err != nil {
log.Fatal("error initializing arangodb:", err) log.Fatal("=> error setting sql provider:", err)
}
Mgr = &manager{
sqlDB: nil,
arangodb: arangodb,
mongodb: nil,
}
break
case enum.Mongodb.String():
mongodb, err := initMongodb()
if err != nil {
log.Fatal("error initializing mongodb connection:", err)
}
Mgr = &manager{
sqlDB: nil,
arangodb: nil,
mongodb: mongodb,
} }
} }
// common for all sql dbs that are configured via go-orm if isArangoDB {
if IsORMSupported { Provider, err = arangodb.NewProvider()
if err != nil { if err != nil {
log.Fatal("Failed to init sqlDB:", err) log.Fatal("=> error setting arangodb provider:", err)
} else {
sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{})
} }
Mgr = &manager{ }
sqlDB: sqlDB,
arangodb: nil, if isMongoDB {
mongodb: nil, Provider, err = mongodb.NewProvider()
if err != nil {
log.Fatal("=> error setting arangodb provider:", err)
}
}
if isFaunaDB {
Provider, err = faunadb.NewProvider()
if err != nil {
log.Fatal("=> error setting arangodb provider:", err)
} }
} }
} }

11
server/db/models/env.go Normal file
View File

@@ -0,0 +1,11 @@
package models
// Env model for db
type Env struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
EnvData []byte `gorm:"type:text" json:"env" bson:"env"`
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"`
}

21
server/db/models/model.go Normal file
View File

@@ -0,0 +1,21 @@
package models
// Collections / Tables available for authorizer in the database
type CollectionList struct {
User string
VerificationRequest string
Session string
Env string
}
var (
// Prefix for table name / collection names
Prefix = "authorizer_"
// Collections / Tables available for authorizer in the database (used for dbs other than gorm)
Collections = CollectionList{
User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions",
Env: Prefix + "env",
}
)

View File

@@ -0,0 +1,13 @@
package models
// Session model for db
type Session struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id"`
User User `json:"-" bson:"-"`
UserAgent string `json:"user_agent" bson:"user_agent"`
IP string `json:"ip" bson:"ip"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
}

54
server/db/models/user.go Normal file
View File

@@ -0,0 +1,54 @@
package models
import (
"strings"
"github.com/authorizerdev/authorizer/server/graph/model"
)
// User model for db
type User struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
Email string `gorm:"unique" json:"email" bson:"email"`
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at"`
Password *string `gorm:"type:text" json:"password" bson:"password"`
SignupMethods string `json:"signup_methods" bson:"signup_methods"`
GivenName *string `json:"given_name" bson:"given_name"`
FamilyName *string `json:"family_name" bson:"family_name"`
MiddleName *string `json:"middle_name" bson:"middle_name"`
Nickname *string `json:"nickname" bson:"nickname"`
Gender *string `json:"gender" bson:"gender"`
Birthdate *string `json:"birthdate" bson:"birthdate"`
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number"`
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
Roles string `json:"roles" bson:"roles"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
}
func (user *User) AsAPIUser() *model.User {
isEmailVerified := user.EmailVerifiedAt != nil
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
return &model.User{
ID: user.ID,
Email: user.Email,
EmailVerified: isEmailVerified,
SignupMethods: user.SignupMethods,
GivenName: user.GivenName,
FamilyName: user.FamilyName,
MiddleName: user.MiddleName,
Nickname: user.Nickname,
PreferredUsername: &user.Email,
Gender: user.Gender,
Birthdate: user.Birthdate,
PhoneNumber: user.PhoneNumber,
PhoneNumberVerified: &isPhoneVerified,
Picture: user.Picture,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
}

View File

@@ -0,0 +1,13 @@
package models
// VerificationRequest model for db
type VerificationRequest struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
Token string `gorm:"type:text" json:"token" bson:"token"`
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
}

View File

@@ -0,0 +1,123 @@
package arangodb
import (
"context"
"log"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/arangodb/go-driver/http"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
)
type provider struct {
db arangoDriver.Database
}
// for this we need arangodb instance up and running
// for local testing we can use dockerized version of it
// docker run -p 8529:8529 -e ARANGO_ROOT_PASSWORD=root arangodb/arangodb:3.8.4
// NewProvider to initialize arangodb connection
func NewProvider() (*provider, error) {
ctx := context.Background()
conn, err := http.NewConnection(http.ConnectionConfig{
Endpoints: []string{envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)},
})
if err != nil {
return nil, err
}
arangoClient, err := arangoDriver.NewClient(arangoDriver.ClientConfig{
Connection: conn,
})
if err != nil {
return nil, err
}
var arangodb driver.Database
arangodb_exists, err := arangoClient.DatabaseExists(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
if arangodb_exists {
log.Println(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName) + " db exists already")
arangodb, err = arangoClient.Database(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName))
if err != nil {
return nil, err
}
} else {
arangodb, err = arangoClient.CreateDatabase(nil, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), nil)
if err != nil {
return nil, err
}
}
userCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.User)
if userCollectionExists {
log.Println(models.Collections.User + " collection exists already")
} else {
_, err = arangodb.CreateCollection(ctx, models.Collections.User, nil)
if err != nil {
log.Println("error creating collection("+models.Collections.User+"):", err)
}
}
userCollection, _ := arangodb.Collection(nil, models.Collections.User)
userCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
userCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
verificationRequestCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.VerificationRequest)
if verificationRequestCollectionExists {
log.Println(models.Collections.VerificationRequest + " collection exists already")
} else {
_, err = arangodb.CreateCollection(ctx, models.Collections.VerificationRequest, nil)
if err != nil {
log.Println("error creating collection("+models.Collections.VerificationRequest+"):", err)
}
}
verificationRequestCollection, _ := arangodb.Collection(nil, models.Collections.VerificationRequest)
verificationRequestCollection.EnsureHashIndex(ctx, []string{"email", "identifier"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
verificationRequestCollection.EnsureHashIndex(ctx, []string{"token"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true,
})
sessionCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Session)
if sessionCollectionExists {
log.Println(models.Collections.Session + " collection exists already")
} else {
_, err = arangodb.CreateCollection(ctx, models.Collections.Session, nil)
if err != nil {
log.Println("error creating collection("+models.Collections.Session+"):", err)
}
}
sessionCollection, _ := arangodb.Collection(nil, models.Collections.Session)
sessionCollection.EnsureHashIndex(ctx, []string{"user_id"}, &arangoDriver.EnsureHashIndexOptions{
Sparse: true,
})
configCollectionExists, err := arangodb.CollectionExists(ctx, models.Collections.Env)
if configCollectionExists {
log.Println(models.Collections.Env + " collection exists already")
} else {
_, err = arangodb.CreateCollection(ctx, models.Collections.Env, nil)
if err != nil {
log.Println("error creating collection("+models.Collections.Env+"):", err)
}
}
return &provider{
db: arangodb,
}, err
}

View File

@@ -0,0 +1,73 @@
package arangodb
import (
"fmt"
"log"
"time"
arangoDriver "github.com/arangodb/go-driver"
"github.com/google/uuid"
"github.com/authorizerdev/authorizer/server/db/models"
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
env.CreatedAt = time.Now().Unix()
env.UpdatedAt = time.Now().Unix()
configCollection, _ := p.db.Collection(nil, models.Collections.Env)
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(nil), env)
if err != nil {
log.Println("error adding config:", err)
return env, err
}
env.Key = meta.Key
env.ID = meta.ID.String()
return env, nil
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
collection, _ := p.db.Collection(nil, models.Collections.Env)
meta, err := collection.UpdateDocument(nil, env.Key, env)
if err != nil {
log.Println("error updating config:", err)
return env, err
}
env.Key = meta.Key
env.ID = meta.ID.String()
return env, nil
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
var env models.Env
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.Env)
cursor, err := p.db.Query(nil, query, nil)
if err != nil {
return env, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if env.Key == "" {
return env, fmt.Errorf("config not found")
}
break
}
_, err := cursor.ReadDocument(nil, &env)
if err != nil {
return env, err
}
}
return env, nil
}

View File

@@ -0,0 +1,42 @@
package arangodb
import (
"fmt"
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection, _ := p.db.Collection(nil, models.Collections.Session)
_, err := sessionCollection.CreateDocument(nil, session)
if err != nil {
log.Println(`error saving session`, err)
return err
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @userId REMOVE { _key: d._key } IN %s`, models.Collections.Session, models.Collections.Session)
bindVars := map[string]interface{}{
"userId": userId,
}
cursor, err := p.db.Query(nil, query, bindVars)
if err != nil {
log.Println("=> error deleting arangodb session:", err)
return err
}
defer cursor.Close()
return nil
}

View File

@@ -0,0 +1,157 @@
package arangodb
import (
"fmt"
"log"
"strings"
"time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/google/uuid"
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
userCollection, _ := p.db.Collection(nil, models.Collections.User)
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(nil), user)
if err != nil {
log.Println("error adding user:", err)
return user, err
}
user.Key = meta.Key
user.ID = meta.ID.String()
return user, nil
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
collection, _ := p.db.Collection(nil, models.Collections.User)
meta, err := collection.UpdateDocument(nil, user.Key, user)
if err != nil {
log.Println("error updating user:", err)
return user, err
}
user.Key = meta.Key
user.ID = meta.ID.String()
return user, nil
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
collection, _ := p.db.Collection(nil, models.Collections.User)
_, err := collection.RemoveDocument(nil, user.Key)
if err != nil {
log.Println(`error deleting user:`, err)
return err
}
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers() ([]models.User, error) {
var users []models.User
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.User)
cursor, err := p.db.Query(nil, query, nil)
if err != nil {
return users, err
}
defer cursor.Close()
for {
var user models.User
meta, err := cursor.ReadDocument(nil, &user)
if driver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return users, err
}
if meta.Key != "" {
users = append(users, user)
}
}
return users, nil
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
var user models.User
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email RETURN d", models.Collections.User)
bindVars := map[string]interface{}{
"email": email,
}
cursor, err := p.db.Query(nil, query, bindVars)
if err != nil {
return user, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if user.Key == "" {
return user, fmt.Errorf("user not found")
}
break
}
_, err := cursor.ReadDocument(nil, &user)
if err != nil {
return user, err
}
}
return user, nil
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
var user models.User
query := fmt.Sprintf("FOR d in %s FILTER d._id == @id LIMIT 1 RETURN d", models.Collections.User)
bindVars := map[string]interface{}{
"id": id,
}
cursor, err := p.db.Query(nil, query, bindVars)
if err != nil {
return user, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if user.Key == "" {
return user, fmt.Errorf("user not found")
}
break
}
_, err := cursor.ReadDocument(nil, &user)
if err != nil {
return user, err
}
}
return user, nil
}

View File

@@ -0,0 +1,135 @@
package arangodb
import (
"fmt"
"log"
"time"
"github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
verificationRequestRequestCollection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
meta, err := verificationRequestRequestCollection.CreateDocument(nil, verificationRequest)
if err != nil {
log.Println("error saving verificationRequest record:", err)
return verificationRequest, err
}
verificationRequest.Key = meta.Key
verificationRequest.ID = meta.ID.String()
return verificationRequest, nil
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
query := fmt.Sprintf("FOR d in %s FILTER d.token == @token LIMIT 1 RETURN d", models.Collections.VerificationRequest)
bindVars := map[string]interface{}{
"token": token,
}
cursor, err := p.db.Query(nil, query, bindVars)
if err != nil {
return verificationRequest, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if verificationRequest.Key == "" {
return verificationRequest, fmt.Errorf("verification request not found")
}
break
}
_, err := cursor.ReadDocument(nil, &verificationRequest)
if err != nil {
return verificationRequest, err
}
}
return verificationRequest, nil
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email FILTER d.identifier == @identifier LIMIT 1 RETURN d", models.Collections.VerificationRequest)
bindVars := map[string]interface{}{
"email": email,
"identifier": identifier,
}
cursor, err := p.db.Query(nil, query, bindVars)
if err != nil {
return verificationRequest, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if verificationRequest.Key == "" {
return verificationRequest, fmt.Errorf("verification request not found")
}
break
}
_, err := cursor.ReadDocument(nil, &verificationRequest)
if err != nil {
return verificationRequest, err
}
}
return verificationRequest, nil
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
var verificationRequests []models.VerificationRequest
query := fmt.Sprintf("FOR d in %s RETURN d", models.Collections.VerificationRequest)
cursor, err := p.db.Query(nil, query, nil)
if err != nil {
return verificationRequests, err
}
defer cursor.Close()
for {
var verificationRequest models.VerificationRequest
meta, err := cursor.ReadDocument(nil, &verificationRequest)
if driver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return verificationRequests, err
}
if meta.Key != "" {
verificationRequests = append(verificationRequests, verificationRequest)
}
}
return verificationRequests, nil
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
collection, _ := p.db.Collection(nil, models.Collections.VerificationRequest)
_, err := collection.RemoveDocument(nil, verificationRequest.Key)
if err != nil {
log.Println(`error deleting verification request:`, err)
return err
}
return nil
}

View File

@@ -0,0 +1,51 @@
package faunadb
import (
"log"
"time"
f "github.com/fauna/faunadb-go/v5/faunadb"
"github.com/google/uuid"
"github.com/authorizerdev/authorizer/server/db/models"
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
env.Key = env.ID
}
env.CreatedAt = time.Now().Unix()
env.UpdatedAt = time.Now().Unix()
_, err := p.db.Query(
f.Create(
f.Collection(models.Collections.Env),
f.Obj{
"data": env,
},
),
)
if err != nil {
log.Println("error adding env:", err)
return env, err
}
return env, nil
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
return env, nil
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
var env models.Env
return env, nil
}

View File

@@ -0,0 +1,164 @@
package faunadb
import (
"errors"
"log"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
f "github.com/fauna/faunadb-go/v5/faunadb"
)
type provider struct {
db *f.FaunaClient
}
// NewProvider returns a new faunadb provider
func NewProvider() (*provider, error) {
secret := ""
dbURL := "https://db.fauna.com"
// secret,url is stored in DATABASE_URL
dbURLSplit := strings.Split(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL), ":")
secret = dbURLSplit[0]
if len(dbURLSplit) > 1 {
dbURL = dbURLSplit[1]
}
client := f.NewFaunaClient(secret, f.Endpoint(dbURL))
if client == nil {
return nil, errors.New("failed to create faunadb client")
}
_, err := client.Query(
f.CreateCollection(f.Obj{"name": models.Collections.Env}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "env_id",
"source": f.Collection(models.Collections.Env),
"values": "_id",
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "env_key",
"source": f.Collection(models.Collections.Env),
"values": "_key",
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateCollection(f.Obj{"name": models.Collections.User}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_id",
"source": f.Collection(models.Collections.User),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_key",
"source": f.Collection(models.Collections.User),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "email",
"source": f.Collection(models.Collections.User),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateCollection(f.Obj{"name": models.Collections.Session}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_id",
"source": f.Collection(models.Collections.Session),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_key",
"source": f.Collection(models.Collections.Session),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateCollection(f.Obj{"name": models.Collections.VerificationRequest}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_id",
"source": f.Collection(models.Collections.VerificationRequest),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
_, err = client.Query(
f.CreateIndex(
f.Obj{
"name": "_key",
"source": f.Collection(models.Collections.VerificationRequest),
"unique": true,
}))
if err != nil {
log.Println("error:", err)
}
return &provider{
db: client,
}, nil
}

View File

@@ -0,0 +1,25 @@
package faunadb
import (
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
return nil
}

View File

@@ -0,0 +1,60 @@
package faunadb
import (
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/google/uuid"
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
return user, nil
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
return user, nil
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers() ([]models.User, error) {
var users []models.User
return users, nil
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
var user models.User
return user, nil
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
var user models.User
return user, nil
}

View File

@@ -0,0 +1,46 @@
package faunadb
import (
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
return verificationRequest, nil
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
return verificationRequest, nil
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
return verificationRequest, nil
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
var verificationRequests []models.VerificationRequest
return verificationRequests, nil
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
return nil
}

View File

@@ -0,0 +1,66 @@
package mongodb
import (
"fmt"
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
env.CreatedAt = time.Now().Unix()
env.UpdatedAt = time.Now().Unix()
env.Key = env.ID
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
_, err := configCollection.InsertOne(nil, env)
if err != nil {
log.Println("error adding config:", err)
return env, err
}
return env, nil
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
_, err := configCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": env.ID}}, bson.M{"$set": env}, options.MergeUpdateOptions())
if err != nil {
log.Println("error updating config:", err)
return env, err
}
return env, nil
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
var env models.Env
configCollection := p.db.Collection(models.Collections.Env, options.Collection())
cursor, err := configCollection.Find(nil, bson.M{}, options.Find())
if err != nil {
return env, err
}
defer cursor.Close(nil)
for cursor.Next(nil) {
err := cursor.Decode(&env)
if err != nil {
return env, err
}
}
if env.ID == "" {
return env, fmt.Errorf("config not found")
}
return env, nil
}

View File

@@ -1,18 +1,25 @@
package db package mongodb
import ( import (
"context" "context"
"time" "time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
"go.mongodb.org/mongo-driver/mongo/readpref" "go.mongodb.org/mongo-driver/mongo/readpref"
) )
func initMongodb() (*mongo.Database, error) { type provider struct {
mongodbOptions := options.Client().ApplyURI(constants.DATABASE_URL) db *mongo.Database
}
// NewProvider to initialize mongodb connection
func NewProvider() (*provider, error) {
mongodbOptions := options.Client().ApplyURI(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL))
maxWait := time.Duration(5 * time.Second) maxWait := time.Duration(5 * time.Second)
mongodbOptions.ConnectTimeout = &maxWait mongodbOptions.ConnectTimeout = &maxWait
mongoClient, err := mongo.NewClient(mongodbOptions) mongoClient, err := mongo.NewClient(mongodbOptions)
@@ -30,10 +37,10 @@ func initMongodb() (*mongo.Database, error) {
return nil, err return nil, err
} }
mongodb := mongoClient.Database(constants.DATABASE_NAME, options.Database()) mongodb := mongoClient.Database(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database())
mongodb.CreateCollection(ctx, Collections.User, options.CreateCollection()) mongodb.CreateCollection(ctx, models.Collections.User, options.CreateCollection())
userCollection := mongodb.Collection(Collections.User, options.Collection()) userCollection := mongodb.Collection(models.Collections.User, options.Collection())
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{ mongo.IndexModel{
Keys: bson.M{"email": 1}, Keys: bson.M{"email": 1},
@@ -49,8 +56,8 @@ func initMongodb() (*mongo.Database, error) {
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())
mongodb.CreateCollection(ctx, Collections.VerificationRequest, options.CreateCollection()) mongodb.CreateCollection(ctx, models.Collections.VerificationRequest, options.CreateCollection())
verificationRequestCollection := mongodb.Collection(Collections.VerificationRequest, options.Collection()) verificationRequestCollection := mongodb.Collection(models.Collections.VerificationRequest, options.Collection())
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{ mongo.IndexModel{
Keys: bson.M{"email": 1, "identifier": 1}, Keys: bson.M{"email": 1, "identifier": 1},
@@ -64,8 +71,8 @@ func initMongodb() (*mongo.Database, error) {
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())
mongodb.CreateCollection(ctx, Collections.Session, options.CreateCollection()) mongodb.CreateCollection(ctx, models.Collections.Session, options.CreateCollection())
sessionCollection := mongodb.Collection(Collections.Session, options.Collection()) sessionCollection := mongodb.Collection(models.Collections.Session, options.Collection())
sessionCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ sessionCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{ mongo.IndexModel{
Keys: bson.M{"user_id": 1}, Keys: bson.M{"user_id": 1},
@@ -73,5 +80,9 @@ func initMongodb() (*mongo.Database, error) {
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())
return mongodb, nil mongodb.CreateCollection(ctx, models.Collections.Env, options.CreateCollection())
return &provider{
db: mongodb,
}, nil
} }

View File

@@ -0,0 +1,40 @@
package mongodb
import (
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
session.Key = session.ID
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
_, err := sessionCollection.InsertOne(nil, session)
if err != nil {
log.Println(`error saving session`, err)
return err
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
sessionCollection := p.db.Collection(models.Collections.Session, options.Collection())
_, err := sessionCollection.DeleteMany(nil, bson.M{"user_id": userId}, options.Delete())
if err != nil {
log.Println("error deleting session:", err)
return err
}
return nil
}

View File

@@ -0,0 +1,108 @@
package mongodb
import (
"log"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
user.Key = user.ID
userCollection := p.db.Collection(models.Collections.User, options.Collection())
_, err := userCollection.InsertOne(nil, user)
if err != nil {
log.Println("error adding user:", err)
return user, err
}
return user, nil
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
userCollection := p.db.Collection(models.Collections.User, options.Collection())
_, err := userCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
if err != nil {
log.Println("error updating user:", err)
return user, err
}
return user, nil
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
userCollection := p.db.Collection(models.Collections.User, options.Collection())
_, err := userCollection.DeleteOne(nil, bson.M{"_id": user.ID}, options.Delete())
if err != nil {
log.Println("error deleting user:", err)
return err
}
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers() ([]models.User, error) {
var users []models.User
userCollection := p.db.Collection(models.Collections.User, options.Collection())
cursor, err := userCollection.Find(nil, bson.M{}, options.Find())
if err != nil {
log.Println("error getting users:", err)
return users, err
}
defer cursor.Close(nil)
for cursor.Next(nil) {
var user models.User
err := cursor.Decode(&user)
if err != nil {
return users, err
}
users = append(users, user)
}
return users, nil
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
var user models.User
userCollection := p.db.Collection(models.Collections.User, options.Collection())
err := userCollection.FindOne(nil, bson.M{"email": email}).Decode(&user)
if err != nil {
return user, err
}
return user, nil
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
var user models.User
userCollection := p.db.Collection(models.Collections.User, options.Collection())
err := userCollection.FindOne(nil, bson.M{"_id": id}).Decode(&user)
if err != nil {
return user, err
}
return user, nil
}

View File

@@ -0,0 +1,91 @@
package mongodb
import (
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
verificationRequest.Key = verificationRequest.ID
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.InsertOne(nil, verificationRequest)
if err != nil {
log.Println("error saving verification record:", err)
return verificationRequest, err
}
}
return verificationRequest, nil
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(nil, bson.M{"token": token}).Decode(&verificationRequest)
if err != nil {
return verificationRequest, err
}
return verificationRequest, nil
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(nil, bson.M{"email": email, "identifier": identifier}).Decode(&verificationRequest)
if err != nil {
return verificationRequest, err
}
return verificationRequest, nil
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
var verificationRequests []models.VerificationRequest
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
cursor, err := verificationRequestCollection.Find(nil, bson.M{}, options.Find())
if err != nil {
log.Println("error getting verification requests:", err)
return verificationRequests, err
}
defer cursor.Close(nil)
for cursor.Next(nil) {
var verificationRequest models.VerificationRequest
err := cursor.Decode(&verificationRequest)
if err != nil {
return verificationRequests, err
}
verificationRequests = append(verificationRequests, verificationRequest)
}
return verificationRequests, nil
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
verificationRequestCollection := p.db.Collection(models.Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.DeleteOne(nil, bson.M{"_id": verificationRequest.ID}, options.Delete())
if err != nil {
log.Println("error deleting verification request::", err)
return err
}
return nil
}

View File

@@ -0,0 +1,41 @@
package providers
import "github.com/authorizerdev/authorizer/server/db/models"
type Provider interface {
// AddUser to save user information in database
AddUser(user models.User) (models.User, error)
// UpdateUser to update user information in database
UpdateUser(user models.User) (models.User, error)
// DeleteUser to delete user information from database
DeleteUser(user models.User) error
// ListUsers to get list of users from database
ListUsers() ([]models.User, error)
// GetUserByEmail to get user information from database using email address
GetUserByEmail(email string) (models.User, error)
// GetUserByID to get user information from database using user ID
GetUserByID(id string) (models.User, error)
// AddVerification to save verification request in database
AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error)
// GetVerificationRequestByToken to get verification request from database using token
GetVerificationRequestByToken(token string) (models.VerificationRequest, error)
// GetVerificationRequestByEmail to get verification request by email from database
GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error)
// ListVerificationRequests to get list of verification requests from database
ListVerificationRequests() ([]models.VerificationRequest, error)
// DeleteVerificationRequest to delete verification request from database
DeleteVerificationRequest(verificationRequest models.VerificationRequest) error
// AddSession to save session information in database
AddSession(session models.Session) error
// DeleteSession to delete session information from database
DeleteSession(userId string) error
// AddEnv to save environment information in database
AddEnv(env models.Env) (models.Env, error)
// UpdateEnv to update environment information in database
UpdateEnv(env models.Env) (models.Env, error)
// GetEnv to get environment information from database
GetEnv() (models.Env, error)
}

View File

@@ -0,0 +1,49 @@
package sql
import (
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
)
// AddEnv to save environment information in database
func (p *provider) AddEnv(env models.Env) (models.Env, error) {
if env.ID == "" {
env.ID = uuid.New().String()
}
env.Key = env.ID
result := p.db.Create(&env)
if result.Error != nil {
log.Println("error adding config:", result.Error)
return env, result.Error
}
return env, nil
}
// UpdateEnv to update environment information in database
func (p *provider) UpdateEnv(env models.Env) (models.Env, error) {
env.UpdatedAt = time.Now().Unix()
result := p.db.Save(&env)
if result.Error != nil {
log.Println("error updating config:", result.Error)
return env, result.Error
}
return env, nil
}
// GetEnv to get environment information from database
func (p *provider) GetEnv() (models.Env, error) {
var env models.Env
result := p.db.First(&env)
if result.Error != nil {
return env, result.Error
}
return env, nil
}

View File

@@ -0,0 +1,38 @@
package sql
import (
"log"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
"gorm.io/gorm/clause"
)
// AddSession to save session information in database
func (p *provider) AddSession(session models.Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
session.Key = session.ID
res := p.db.Clauses(
clause.OnConflict{
DoNothing: true,
}).Create(&session)
if res.Error != nil {
log.Println(`error saving session`, res.Error)
return res.Error
}
return nil
}
// DeleteSession to delete session information from database
func (p *provider) DeleteSession(userId string) error {
result := p.db.Where("user_id = ?", userId).Delete(&models.Session{})
if result.Error != nil {
log.Println(`error deleting session:`, result.Error)
return result.Error
}
return nil
}

View File

@@ -0,0 +1,53 @@
package sql
import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"gorm.io/driver/mysql"
"gorm.io/driver/postgres"
"gorm.io/driver/sqlite"
"gorm.io/driver/sqlserver"
"gorm.io/gorm"
"gorm.io/gorm/schema"
)
type provider struct {
db *gorm.DB
}
// NewProvider returns a new SQL provider
func NewProvider() (*provider, error) {
var sqlDB *gorm.DB
var err error
ormConfig := &gorm.Config{
NamingStrategy: schema.NamingStrategy{
TablePrefix: models.Prefix,
},
}
switch envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) {
case constants.DbTypePostgres:
sqlDB, err = gorm.Open(postgres.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
break
case constants.DbTypeSqlite:
sqlDB, err = gorm.Open(sqlite.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
break
case constants.DbTypeMysql:
sqlDB, err = gorm.Open(mysql.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
break
case constants.DbTypeSqlserver:
sqlDB, err = gorm.Open(sqlserver.Open(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseURL)), ormConfig)
break
}
if err != nil {
return nil, err
}
sqlDB.AutoMigrate(&models.User{}, &models.VerificationRequest{}, &models.Session{}, &models.Env{})
return &provider{
db: sqlDB,
}, nil
}

View File

@@ -0,0 +1,101 @@
package sql
import (
"log"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/google/uuid"
"gorm.io/gorm/clause"
)
// AddUser to save user information in database
func (p *provider) AddUser(user models.User) (models.User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
user.Key = user.ID
result := p.db.Clauses(
clause.OnConflict{
UpdateAll: true,
Columns: []clause.Column{{Name: "email"}},
}).Create(&user)
if result.Error != nil {
log.Println("error adding user:", result.Error)
return user, result.Error
}
return user, nil
}
// UpdateUser to update user information in database
func (p *provider) UpdateUser(user models.User) (models.User, error) {
user.UpdatedAt = time.Now().Unix()
result := p.db.Save(&user)
if result.Error != nil {
log.Println("error updating user:", result.Error)
return user, result.Error
}
return user, nil
}
// DeleteUser to delete user information from database
func (p *provider) DeleteUser(user models.User) error {
result := p.db.Delete(&user)
if result.Error != nil {
log.Println(`error deleting user:`, result.Error)
return result.Error
}
return nil
}
// ListUsers to get list of users from database
func (p *provider) ListUsers() ([]models.User, error) {
var users []models.User
result := p.db.Find(&users)
if result.Error != nil {
log.Println("error getting users:", result.Error)
return users, result.Error
}
return users, nil
}
// GetUserByEmail to get user information from database using email address
func (p *provider) GetUserByEmail(email string) (models.User, error) {
var user models.User
result := p.db.Where("email = ?", email).First(&user)
if result.Error != nil {
return user, result.Error
}
return user, nil
}
// GetUserByID to get user information from database using user ID
func (p *provider) GetUserByID(id string) (models.User, error) {
var user models.User
result := p.db.Where("id = ?", id).First(&user)
if result.Error != nil {
return user, result.Error
}
return user, nil
}

View File

@@ -0,0 +1,80 @@
package sql
import (
"log"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
"gorm.io/gorm/clause"
)
// AddVerification to save verification request in database
func (p *provider) AddVerificationRequest(verificationRequest models.VerificationRequest) (models.VerificationRequest, error) {
if verificationRequest.ID == "" {
verificationRequest.ID = uuid.New().String()
}
verificationRequest.Key = verificationRequest.ID
result := p.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}),
}).Create(&verificationRequest)
if result.Error != nil {
log.Println(`error saving verification request record`, result.Error)
return verificationRequest, result.Error
}
return verificationRequest, nil
}
// GetVerificationRequestByToken to get verification request from database using token
func (p *provider) GetVerificationRequestByToken(token string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
result := p.db.Where("token = ?", token).First(&verificationRequest)
if result.Error != nil {
log.Println(`error getting verification request:`, result.Error)
return verificationRequest, result.Error
}
return verificationRequest, nil
}
// GetVerificationRequestByEmail to get verification request by email from database
func (p *provider) GetVerificationRequestByEmail(email string, identifier string) (models.VerificationRequest, error) {
var verificationRequest models.VerificationRequest
result := p.db.Where("email = ? AND identifier = ?", email, identifier).First(&verificationRequest)
if result.Error != nil {
log.Println(`error getting verification token:`, result.Error)
return verificationRequest, result.Error
}
return verificationRequest, nil
}
// ListVerificationRequests to get list of verification requests from database
func (p *provider) ListVerificationRequests() ([]models.VerificationRequest, error) {
var verificationRequests []models.VerificationRequest
result := p.db.Find(&verificationRequests)
if result.Error != nil {
log.Println("error getting verification requests:", result.Error)
return verificationRequests, result.Error
}
return verificationRequests, nil
}
// DeleteVerificationRequest to delete verification request from database
func (p *provider) DeleteVerificationRequest(verificationRequest models.VerificationRequest) error {
result := p.db.Delete(&verificationRequest)
if result.Error != nil {
log.Println(`error deleting verification request:`, result.Error)
return result.Error
}
return nil
}

View File

@@ -1,102 +0,0 @@
package db
import (
"fmt"
"log"
"time"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
"gorm.io/gorm/clause"
)
type Session struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id"`
User User `json:"-" bson:"-"`
UserAgent string `json:"user_agent" bson:"user_agent"`
IP string `json:"ip" bson:"ip"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
}
// AddSession function to save user sessiosn
func (mgr *manager) AddSession(session Session) error {
if session.ID == "" {
session.ID = uuid.New().String()
}
if IsORMSupported {
session.Key = session.ID
res := mgr.sqlDB.Clauses(
clause.OnConflict{
DoNothing: true,
}).Create(&session)
if res.Error != nil {
log.Println(`error saving session`, res.Error)
return res.Error
}
}
if IsArangoDB {
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection, _ := mgr.arangodb.Collection(nil, Collections.Session)
_, err := sessionCollection.CreateDocument(nil, session)
if err != nil {
log.Println(`error saving session`, err)
return err
}
}
if IsMongoDB {
session.Key = session.ID
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
sessionCollection := mgr.mongodb.Collection(Collections.Session, options.Collection())
_, err := sessionCollection.InsertOne(nil, session)
if err != nil {
log.Println(`error saving session`, err)
return err
}
}
return nil
}
func (mgr *manager) DeleteUserSession(userId string) error {
if IsORMSupported {
result := mgr.sqlDB.Where("user_id = ?", userId).Delete(&Session{})
if result.Error != nil {
log.Println(`error deleting session:`, result.Error)
return result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @userId REMOVE { _key: d._key } IN %s`, Collections.Session, Collections.Session)
bindVars := map[string]interface{}{
"userId": userId,
}
cursor, err := mgr.arangodb.Query(nil, query, bindVars)
if err != nil {
log.Println("=> error deleting arangodb session:", err)
return err
}
defer cursor.Close()
}
if IsMongoDB {
sessionCollection := mgr.mongodb.Collection(Collections.Session, options.Collection())
_, err := sessionCollection.DeleteMany(nil, bson.M{"user_id": userId}, options.Delete())
if err != nil {
log.Println("error deleting session:", err)
return err
}
}
return nil
}

View File

@@ -1,313 +0,0 @@
package db
import (
"fmt"
"log"
"time"
"github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
"gorm.io/gorm/clause"
)
type User struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
Email string `gorm:"unique" json:"email" bson:"email"`
EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at"`
Password *string `gorm:"type:text" json:"password" bson:"password"`
SignupMethods string `json:"signup_methods" bson:"signup_methods"`
GivenName *string `json:"given_name" bson:"given_name"`
FamilyName *string `json:"family_name" bson:"family_name"`
MiddleName *string `json:"middle_name" bson:"middle_name"`
Nickname *string `json:"nickname" bson:"nickname"`
Gender *string `json:"gender" bson:"gender"`
Birthdate *string `json:"birthdate" bson:"birthdate"`
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number"`
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
Roles string `json:"roles" bson:"roles"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
}
// AddUser function to add user even with email conflict
func (mgr *manager) AddUser(user User) (User, error) {
if user.ID == "" {
user.ID = uuid.New().String()
}
if user.Roles == "" {
user.Roles = constants.DEFAULT_ROLES[0]
}
if IsORMSupported {
// copy id as value for fields required for mongodb & arangodb
user.Key = user.ID
result := mgr.sqlDB.Clauses(
clause.OnConflict{
UpdateAll: true,
Columns: []clause.Column{{Name: "email"}},
}).Create(&user)
if result.Error != nil {
log.Println("error adding user:", result.Error)
return user, result.Error
}
}
if IsArangoDB {
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
userCollection, _ := mgr.arangodb.Collection(nil, Collections.User)
meta, err := userCollection.CreateDocument(arangoDriver.WithOverwrite(nil), user)
if err != nil {
log.Println("error adding user:", err)
return user, err
}
user.Key = meta.Key
user.ID = meta.ID.String()
}
if IsMongoDB {
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
user.Key = user.ID
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
_, err := userCollection.InsertOne(nil, user)
if err != nil {
log.Println("error adding user:", err)
return user, err
}
}
return user, nil
}
// UpdateUser function to update user with ID conflict
func (mgr *manager) UpdateUser(user User) (User, error) {
user.UpdatedAt = time.Now().Unix()
if IsORMSupported {
result := mgr.sqlDB.Save(&user)
if result.Error != nil {
log.Println("error updating user:", result.Error)
return user, result.Error
}
}
if IsArangoDB {
collection, _ := mgr.arangodb.Collection(nil, Collections.User)
meta, err := collection.UpdateDocument(nil, user.Key, user)
if err != nil {
log.Println("error updating user:", err)
return user, err
}
user.Key = meta.Key
user.ID = meta.ID.String()
}
if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
_, err := userCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
if err != nil {
log.Println("error updating user:", err)
return user, err
}
}
return user, nil
}
// GetUsers function to get all users
func (mgr *manager) GetUsers() ([]User, error) {
var users []User
if IsORMSupported {
result := mgr.sqlDB.Find(&users)
if result.Error != nil {
log.Println("error getting users:", result.Error)
return users, result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf("FOR d in %s RETURN d", Collections.User)
cursor, err := mgr.arangodb.Query(nil, query, nil)
if err != nil {
return users, err
}
defer cursor.Close()
for {
var user User
meta, err := cursor.ReadDocument(nil, &user)
if driver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return users, err
}
if meta.Key != "" {
users = append(users, user)
}
}
}
if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
cursor, err := userCollection.Find(nil, bson.M{}, options.Find())
if err != nil {
log.Println("error getting users:", err)
return users, err
}
defer cursor.Close(nil)
for cursor.Next(nil) {
var user User
err := cursor.Decode(&user)
if err != nil {
return users, err
}
users = append(users, user)
}
}
return users, nil
}
func (mgr *manager) GetUserByEmail(email string) (User, error) {
var user User
if IsORMSupported {
result := mgr.sqlDB.Where("email = ?", email).First(&user)
if result.Error != nil {
return user, result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email RETURN d", Collections.User)
bindVars := map[string]interface{}{
"email": email,
}
cursor, err := mgr.arangodb.Query(nil, query, bindVars)
if err != nil {
return user, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if user.Key == "" {
return user, fmt.Errorf("user not found")
}
break
}
_, err := cursor.ReadDocument(nil, &user)
if err != nil {
return user, err
}
}
}
if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
err := userCollection.FindOne(nil, bson.M{"email": email}).Decode(&user)
if err != nil {
return user, err
}
}
return user, nil
}
func (mgr *manager) GetUserByID(id string) (User, error) {
var user User
if IsORMSupported {
result := mgr.sqlDB.Where("id = ?", id).First(&user)
if result.Error != nil {
return user, result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf("FOR d in %s FILTER d._id == @id LIMIT 1 RETURN d", Collections.User)
bindVars := map[string]interface{}{
"id": id,
}
cursor, err := mgr.arangodb.Query(nil, query, bindVars)
if err != nil {
return user, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if user.Key == "" {
return user, fmt.Errorf("user not found")
}
break
}
_, err := cursor.ReadDocument(nil, &user)
if err != nil {
return user, err
}
}
}
if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
err := userCollection.FindOne(nil, bson.M{"_id": id}).Decode(&user)
if err != nil {
return user, err
}
}
return user, nil
}
func (mgr *manager) DeleteUser(user User) error {
if IsORMSupported {
result := mgr.sqlDB.Delete(&user)
if result.Error != nil {
log.Println(`error deleting user:`, result.Error)
return result.Error
}
}
if IsArangoDB {
collection, _ := mgr.arangodb.Collection(nil, Collections.User)
_, err := collection.RemoveDocument(nil, user.Key)
if err != nil {
log.Println(`error deleting user:`, err)
return err
}
}
if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
_, err := userCollection.DeleteOne(nil, bson.M{"_id": user.ID}, options.Delete())
if err != nil {
log.Println("error deleting user:", err)
return err
}
}
return nil
}

View File

@@ -1,260 +0,0 @@
package db
import (
"fmt"
"log"
"time"
"github.com/arangodb/go-driver"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
"gorm.io/gorm/clause"
)
type VerificationRequest struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
Token string `gorm:"type:text" json:"token" bson:"token"`
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
}
// AddVerification function to add verification record
func (mgr *manager) AddVerification(verification VerificationRequest) (VerificationRequest, error) {
if verification.ID == "" {
verification.ID = uuid.New().String()
}
if IsORMSupported {
// copy id as value for fields required for mongodb & arangodb
verification.Key = verification.ID
result := mgr.sqlDB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}),
}).Create(&verification)
if result.Error != nil {
log.Println(`error saving verification record`, result.Error)
return verification, result.Error
}
}
if IsArangoDB {
verification.CreatedAt = time.Now().Unix()
verification.UpdatedAt = time.Now().Unix()
verificationRequestCollection, _ := mgr.arangodb.Collection(nil, Collections.VerificationRequest)
meta, err := verificationRequestCollection.CreateDocument(nil, verification)
if err != nil {
log.Println("error saving verification record:", err)
return verification, err
}
verification.Key = meta.Key
verification.ID = meta.ID.String()
}
if IsMongoDB {
verification.CreatedAt = time.Now().Unix()
verification.UpdatedAt = time.Now().Unix()
verification.Key = verification.ID
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.InsertOne(nil, verification)
if err != nil {
log.Println("error saving verification record:", err)
return verification, err
}
}
return verification, nil
}
// GetVerificationRequests function to get all verification requests
func (mgr *manager) GetVerificationRequests() ([]VerificationRequest, error) {
var verificationRequests []VerificationRequest
if IsORMSupported {
result := mgr.sqlDB.Find(&verificationRequests)
if result.Error != nil {
log.Println("error getting verification requests:", result.Error)
return verificationRequests, result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf("FOR d in %s RETURN d", Collections.VerificationRequest)
cursor, err := mgr.arangodb.Query(nil, query, nil)
if err != nil {
return verificationRequests, err
}
defer cursor.Close()
for {
var verificationRequest VerificationRequest
meta, err := cursor.ReadDocument(nil, &verificationRequest)
if driver.IsNoMoreDocuments(err) {
break
} else if err != nil {
return verificationRequests, err
}
if meta.Key != "" {
verificationRequests = append(verificationRequests, verificationRequest)
}
}
}
if IsMongoDB {
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
cursor, err := verificationRequestCollection.Find(nil, bson.M{}, options.Find())
if err != nil {
log.Println("error getting verification requests:", err)
return verificationRequests, err
}
defer cursor.Close(nil)
for cursor.Next(nil) {
var verificationRequest VerificationRequest
err := cursor.Decode(&verificationRequest)
if err != nil {
return verificationRequests, err
}
verificationRequests = append(verificationRequests, verificationRequest)
}
}
return verificationRequests, nil
}
func (mgr *manager) GetVerificationByToken(token string) (VerificationRequest, error) {
var verification VerificationRequest
if IsORMSupported {
result := mgr.sqlDB.Where("token = ?", token).First(&verification)
if result.Error != nil {
log.Println(`error getting verification request:`, result.Error)
return verification, result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf("FOR d in %s FILTER d.token == @token LIMIT 1 RETURN d", Collections.VerificationRequest)
bindVars := map[string]interface{}{
"token": token,
}
cursor, err := mgr.arangodb.Query(nil, query, bindVars)
if err != nil {
return verification, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if verification.Key == "" {
return verification, fmt.Errorf("verification request not found")
}
break
}
_, err := cursor.ReadDocument(nil, &verification)
if err != nil {
return verification, err
}
}
}
if IsMongoDB {
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(nil, bson.M{"token": token}).Decode(&verification)
if err != nil {
return verification, err
}
}
return verification, nil
}
func (mgr *manager) GetVerificationByEmail(email string, identifier string) (VerificationRequest, error) {
var verification VerificationRequest
if IsORMSupported {
result := mgr.sqlDB.Where("email = ? AND identifier = ?", email, identifier).First(&verification)
if result.Error != nil {
log.Println(`error getting verification token:`, result.Error)
return verification, result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email FILTER d.identifier == @identifier LIMIT 1 RETURN d", Collections.VerificationRequest)
bindVars := map[string]interface{}{
"email": email,
"identifier": identifier,
}
cursor, err := mgr.arangodb.Query(nil, query, bindVars)
if err != nil {
return verification, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if verification.Key == "" {
return verification, fmt.Errorf("verification request not found")
}
break
}
_, err := cursor.ReadDocument(nil, &verification)
if err != nil {
return verification, err
}
}
}
if IsMongoDB {
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(nil, bson.M{"email": email, "identifier": identifier}).Decode(&verification)
if err != nil {
return verification, err
}
}
return verification, nil
}
func (mgr *manager) DeleteVerificationRequest(verificationRequest VerificationRequest) error {
if IsORMSupported {
result := mgr.sqlDB.Delete(&verificationRequest)
if result.Error != nil {
log.Println(`error deleting verification request:`, result.Error)
return result.Error
}
}
if IsArangoDB {
collection, _ := mgr.arangodb.Collection(nil, Collections.VerificationRequest)
_, err := collection.RemoveDocument(nil, verificationRequest.Key)
if err != nil {
log.Println(`error deleting verification request:`, err)
return err
}
}
if IsMongoDB {
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.DeleteOne(nil, bson.M{"_id": verificationRequest.ID}, options.Delete())
if err != nil {
log.Println("error deleting verification request::", err)
return err
}
}
return nil
}

View File

@@ -1,23 +1,44 @@
package email package email
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"encoding/json"
"log" "log"
"strconv" "strconv"
"text/template"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
gomail "gopkg.in/mail.v2" gomail "gopkg.in/mail.v2"
) )
// addEmailTemplate is used to add html template in email body
func addEmailTemplate(a string, b map[string]interface{}, templateName string) string {
tmpl, err := template.New(templateName).Parse(a)
if err != nil {
output, _ := json.Marshal(b)
return string(output)
}
buf := &bytes.Buffer{}
err = tmpl.Execute(buf, b)
if err != nil {
panic(err)
}
s := buf.String()
return s
}
// SendMail function to send mail
func SendMail(to []string, Subject, bodyMessage string) error { func SendMail(to []string, Subject, bodyMessage string) error {
m := gomail.NewMessage() m := gomail.NewMessage()
m.SetHeader("From", constants.SENDER_EMAIL) m.SetHeader("From", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail))
m.SetHeader("To", to...) m.SetHeader("To", to...)
m.SetHeader("Subject", Subject) m.SetHeader("Subject", Subject)
m.SetBody("text/html", bodyMessage) m.SetBody("text/html", bodyMessage)
port, _ := strconv.Atoi(constants.SMTP_PORT) port, _ := strconv.Atoi(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort))
d := gomail.NewDialer(constants.SMTP_HOST, port, constants.SMTP_USERNAME, constants.SMTP_PASSWORD) d := gomail.NewDialer(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword))
if constants.ENV == "development" { if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" {
d.TLSConfig = &tls.Config{InsecureSkipVerify: true} d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
} }
if err := d.DialAndSend(m); err != nil { if err := d.DialAndSend(m); err != nil {

View File

@@ -0,0 +1,112 @@
package email
import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
)
// SendForgotPasswordMail to send forgot password email
func SendForgotPasswordMail(toEmail, token, host string) error {
resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
if resetPasswordUrl == "" {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)+"/app/reset-password")
}
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "Reset Password"
message := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="x-apple-disable-message-reformatting">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="telephone=no" name="format-detection">
<title></title>
<!--[if (mso 16)]>
<style type="text/css">
a {}
</style>
<![endif]-->
<!--[if gte mso 9]><style>sup { font-size: 100%% !important; }</style><![endif]-->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG></o:AllowPNG>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body style="font-family: sans-serif;">
<div class="es-wrapper-color">
<!--[if gte mso 9]>
<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t">
<v:fill type="tile" color="#ffffff"></v:fill>
</v:background>
<![endif]-->
<table class="es-wrapper" width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-email-paddings" valign="top">
<table class="es-content esd-footer-popover" cellspacing="0" cellpadding="0" align="center">
<tbody>
<tr>
<td class="esd-stripe" align="center">
<table class="es-content-body" style="border-left:1px solid transparent;border-right:1px solid transparent;border-top:1px solid transparent;border-bottom:1px solid transparent;padding:20px 0px;" width="600" cellspacing="0" cellpadding="0" bgcolor="#ffffff" align="center">
<tbody>
<tr>
<td class="esd-structure es-p20t es-p40b es-p40r es-p40l" esd-custom-block-id="8537" align="left">
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-container-frame" width="518" align="left">
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.org_logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p>
<p>We have received a request to reset password for email: <b>{{.org_name}}</b>. If this is correct, please reset the password clicking the button below.</p> <br/>
<a clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Reset Password</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
</body>
</html>
`
data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["verification_url"] = resetPasswordUrl + "?token=" + token
message = addEmailTemplate(message, data, "reset_password_email.tmpl")
return SendMail(Receiver, Subject, message)
}

View File

@@ -0,0 +1,107 @@
package email
import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
)
// SendVerificationMail to send verification email
func SendVerificationMail(toEmail, token string) error {
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
Subject := "Please verify your email"
message := `
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="x-apple-disable-message-reformatting">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="telephone=no" name="format-detection">
<title></title>
<!--[if (mso 16)]>
<style type="text/css">
a {}
</style>
<![endif]-->
<!--[if gte mso 9]><style>sup { font-size: 100%% !important; }</style><![endif]-->
<!--[if gte mso 9]>
<xml>
<o:OfficeDocumentSettings>
<o:AllowPNG></o:AllowPNG>
<o:PixelsPerInch>96</o:PixelsPerInch>
</o:OfficeDocumentSettings>
</xml>
<![endif]-->
</head>
<body style="font-family: sans-serif;">
<div class="es-wrapper-color">
<!--[if gte mso 9]>
<v:background xmlns:v="urn:schemas-microsoft-com:vml" fill="t">
<v:fill type="tile" color="#ffffff"></v:fill>
</v:background>
<![endif]-->
<table class="es-wrapper" width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-email-paddings" valign="top">
<table class="es-content esd-footer-popover" cellspacing="0" cellpadding="0" align="center">
<tbody>
<tr>
<td class="esd-stripe" align="center">
<table class="es-content-body" style="border-left:1px solid transparent;border-right:1px solid transparent;border-top:1px solid transparent;border-bottom:1px solid transparent;padding:20px 0px;" width="600" cellspacing="0" cellpadding="0" bgcolor="#ffffff" align="center">
<tbody>
<tr>
<td class="esd-structure es-p20t es-p40b es-p40r es-p40l" esd-custom-block-id="8537" align="left">
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-container-frame" width="518" align="left">
<table width="100%%" cellspacing="0" cellpadding="0">
<tbody>
<tr>
<td class="esd-block-image es-m-txt-c es-p5b" style="font-size:0;padding:10px" align="center"><a target="_blank" clicktracking="off"><img src="{{.org_logo}}" alt="icon" style="display: block;" title="icon" width="30"></a></td>
</tr>
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;">
<td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p>
<p>We have received request to verify email for <b>{{.org_name}}</b>. If this is correct, please confirm your email address by clicking the button below.</p> <br/>
<a
clicktracking="off" href="{{.verification_url}}" class="es-button" target="_blank" style="text-decoration: none;padding:10px 15px;background-color: rgba(59,130,246,1);color: #fff;font-size: 1em;border-radius:5px;">Confirm Email</a>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
</div>
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
</body>
</html>
`
data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["verification_url"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/verify_email?token=" + token
message = addEmailTemplate(message, data, "verify_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
return SendMail(Receiver, Subject, message)
}

View File

@@ -1,23 +0,0 @@
package enum
type DbType int
const (
Postgres DbType = iota
Sqlite
Mysql
SQLServer
Arangodb
Mongodb
)
func (d DbType) String() string {
return [...]string{
"postgres",
"sqlite",
"mysql",
"sqlserver",
"arangodb",
"mongodb",
}[d]
}

View File

@@ -1,15 +0,0 @@
package enum
type OAuthProvider int
const (
GoogleProvider OAuthProvider = iota
GithubProvider
)
func (d OAuthProvider) String() string {
return [...]string{
"google_provider",
"github_provider",
}[d]
}

View File

@@ -1,21 +0,0 @@
package enum
type SignupMethod int
const (
BasicAuth SignupMethod = iota
MagicLinkLogin
Google
Github
Facebook
)
func (d SignupMethod) String() string {
return [...]string{
"basic_auth",
"magic_link_login",
"google",
"github",
"facebook",
}[d]
}

View File

@@ -1,15 +0,0 @@
package enum
type TokenType int
const (
RefreshToken TokenType = iota
AccessToken
)
func (d TokenType) String() string {
return [...]string{
"refresh_token",
"access_token",
}[d]
}

View File

@@ -1,17 +0,0 @@
package enum
type VerificationType int
const (
BasicAuthSignup VerificationType = iota
UpdateEmail
ForgotPassword
)
func (d VerificationType) String() string {
return [...]string{
"basic_auth_signup",
"update_email",
"forgot_password",
}[d]
}

247
server/env/env.go vendored
View File

@@ -6,181 +6,186 @@ import (
"strings" "strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/google/uuid"
"github.com/joho/godotenv" "github.com/joho/godotenv"
) )
// build variables // InitEnv to initialize EnvData and through error if required env are not present
var (
ARG_DB_URL *string
ARG_DB_TYPE *string
ARG_AUTHORIZER_URL *string
ARG_ENV_FILE *string
)
// InitEnv -> to initialize env and through error if required env are not present
func InitEnv() { func InitEnv() {
if constants.ENV_PATH == "" { // get clone of current store
constants.ENV_PATH = `.env` envData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
}
if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" { if envData.StringEnv[constants.EnvKeyEnv] == "" {
constants.ENV_PATH = *ARG_ENV_FILE envData.StringEnv[constants.EnvKeyEnv] = os.Getenv("ENV")
} if envData.StringEnv[constants.EnvKeyEnv] == "" {
envData.StringEnv[constants.EnvKeyEnv] = "production"
err := godotenv.Load(constants.ENV_PATH)
if err != nil {
log.Printf("error loading %s file", constants.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.ENV == "" {
constants.ENV = os.Getenv("ENV")
if constants.ENV == "" {
constants.ENV = "production"
} }
if constants.ENV == "production" { if envData.StringEnv[constants.EnvKeyEnv] == "production" {
constants.IS_PROD = true envData.BoolEnv[constants.EnvKeyIsProd] = true
os.Setenv("GIN_MODE", "release") os.Setenv("GIN_MODE", "release")
} else { } else {
constants.IS_PROD = false envData.BoolEnv[constants.EnvKeyIsProd] = false
} }
} }
if constants.DATABASE_TYPE == "" { // set authorizer url to empty string so that fresh url is obtained with every server start
constants.DATABASE_TYPE = os.Getenv("DATABASE_TYPE") envData.StringEnv[constants.EnvKeyAuthorizerURL] = ""
log.Println(constants.DATABASE_TYPE) if envData.StringEnv[constants.EnvKeyAppURL] == "" {
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
}
if ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" { if envData.StringEnv[constants.EnvKeyEnvPath] == "" {
constants.DATABASE_TYPE = *ARG_DB_TYPE envData.StringEnv[constants.EnvKeyEnvPath] = `.env`
}
if envstore.ARG_ENV_FILE != nil && *envstore.ARG_ENV_FILE != "" {
envData.StringEnv[constants.EnvKeyEnvPath] = *envstore.ARG_ENV_FILE
}
err := godotenv.Load(envData.StringEnv[constants.EnvKeyEnvPath])
if err != nil {
log.Printf("error loading %s file", envData.StringEnv[constants.EnvKeyEnvPath])
}
if envData.StringEnv[constants.EnvKeyPort] == "" {
envData.StringEnv[constants.EnvKeyPort] = os.Getenv("PORT")
if envData.StringEnv[constants.EnvKeyPort] == "" {
envData.StringEnv[constants.EnvKeyPort] = "8080"
}
}
if envData.StringEnv[constants.EnvKeyAdminSecret] == "" {
envData.StringEnv[constants.EnvKeyAdminSecret] = os.Getenv("ADMIN_SECRET")
}
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
envData.StringEnv[constants.EnvKeyDatabaseType] = os.Getenv("DATABASE_TYPE")
if envstore.ARG_DB_TYPE != nil && *envstore.ARG_DB_TYPE != "" {
envData.StringEnv[constants.EnvKeyDatabaseType] = *envstore.ARG_DB_TYPE
} }
if constants.DATABASE_TYPE == "" { if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
panic("DATABASE_TYPE is required") panic("DATABASE_TYPE is required")
} }
} }
if constants.DATABASE_URL == "" { if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
constants.DATABASE_URL = os.Getenv("DATABASE_URL") envData.StringEnv[constants.EnvKeyDatabaseURL] = os.Getenv("DATABASE_URL")
if ARG_DB_URL != nil && *ARG_DB_URL != "" { if envstore.ARG_DB_URL != nil && *envstore.ARG_DB_URL != "" {
constants.DATABASE_URL = *ARG_DB_URL envData.StringEnv[constants.EnvKeyDatabaseURL] = *envstore.ARG_DB_URL
} }
if constants.DATABASE_URL == "" { if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
panic("DATABASE_URL is required") panic("DATABASE_URL is required")
} }
} }
if constants.DATABASE_NAME == "" { if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
constants.DATABASE_NAME = os.Getenv("DATABASE_NAME") envData.StringEnv[constants.EnvKeyDatabaseName] = os.Getenv("DATABASE_NAME")
if constants.DATABASE_NAME == "" { if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
constants.DATABASE_NAME = "authorizer" envData.StringEnv[constants.EnvKeyDatabaseName] = "authorizer"
} }
} }
if constants.SMTP_HOST == "" { if envData.StringEnv[constants.EnvKeySmtpHost] == "" {
constants.SMTP_HOST = os.Getenv("SMTP_HOST") envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv("SMTP_HOST")
} }
if constants.SMTP_PORT == "" { if envData.StringEnv[constants.EnvKeySmtpPort] == "" {
constants.SMTP_PORT = os.Getenv("SMTP_PORT") envData.StringEnv[constants.EnvKeySmtpPort] = os.Getenv("SMTP_PORT")
} }
if constants.SMTP_USERNAME == "" { if envData.StringEnv[constants.EnvKeySmtpUsername] == "" {
constants.SMTP_USERNAME = os.Getenv("SMTP_USERNAME") envData.StringEnv[constants.EnvKeySmtpUsername] = os.Getenv("SMTP_USERNAME")
} }
if constants.SMTP_PASSWORD == "" { if envData.StringEnv[constants.EnvKeySmtpPassword] == "" {
constants.SMTP_PASSWORD = os.Getenv("SMTP_PASSWORD") envData.StringEnv[constants.EnvKeySmtpPassword] = os.Getenv("SMTP_PASSWORD")
} }
if constants.SENDER_EMAIL == "" { if envData.StringEnv[constants.EnvKeySenderEmail] == "" {
constants.SENDER_EMAIL = os.Getenv("SENDER_EMAIL") envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv("SENDER_EMAIL")
} }
if constants.JWT_SECRET == "" { if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
constants.JWT_SECRET = os.Getenv("JWT_SECRET") envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv("JWT_SECRET")
} if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
envData.StringEnv[constants.EnvKeyJwtSecret] = uuid.New().String()
if constants.JWT_TYPE == "" {
constants.JWT_TYPE = os.Getenv("JWT_TYPE")
}
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = "role"
} }
} }
if constants.AUTHORIZER_URL == "" { if envData.StringEnv[constants.EnvKeyJwtType] == "" {
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/") envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv("JWT_TYPE")
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
if ARG_AUTHORIZER_URL != nil && *ARG_AUTHORIZER_URL != "" { envData.StringEnv[constants.EnvKeyJwtType] = "HS256"
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
} }
} }
if constants.PORT == "" { if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
constants.PORT = os.Getenv("PORT") envData.StringEnv[constants.EnvKeyJwtRoleClaim] = os.Getenv("JWT_ROLE_CLAIM")
if constants.PORT == "" {
constants.PORT = "8080" if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
envData.StringEnv[constants.EnvKeyJwtRoleClaim] = "role"
} }
} }
if constants.REDIS_URL == "" { if envData.StringEnv[constants.EnvKeyRedisURL] == "" {
constants.REDIS_URL = os.Getenv("REDIS_URL") envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv("REDIS_URL")
} }
if constants.COOKIE_NAME == "" { if envData.StringEnv[constants.EnvKeyCookieName] == "" {
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME") envData.StringEnv[constants.EnvKeyCookieName] = os.Getenv("COOKIE_NAME")
if envData.StringEnv[constants.EnvKeyCookieName] == "" {
envData.StringEnv[constants.EnvKeyCookieName] = "authorizer"
}
} }
if constants.GOOGLE_CLIENT_ID == "" { if envData.StringEnv[constants.EnvKeyGoogleClientID] == "" {
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID") envData.StringEnv[constants.EnvKeyGoogleClientID] = os.Getenv("GOOGLE_CLIENT_ID")
} }
if constants.GOOGLE_CLIENT_SECRET == "" { if envData.StringEnv[constants.EnvKeyGoogleClientSecret] == "" {
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET") envData.StringEnv[constants.EnvKeyGoogleClientSecret] = os.Getenv("GOOGLE_CLIENT_SECRET")
} }
if constants.GITHUB_CLIENT_ID == "" { if envData.StringEnv[constants.EnvKeyGithubClientID] == "" {
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID") envData.StringEnv[constants.EnvKeyGithubClientID] = os.Getenv("GITHUB_CLIENT_ID")
} }
if constants.GITHUB_CLIENT_SECRET == "" { if envData.StringEnv[constants.EnvKeyGithubClientSecret] == "" {
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET") envData.StringEnv[constants.EnvKeyGithubClientSecret] = os.Getenv("GITHUB_CLIENT_SECRET")
} }
if constants.FACEBOOK_CLIENT_ID == "" { if envData.StringEnv[constants.EnvKeyFacebookClientID] == "" {
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID") envData.StringEnv[constants.EnvKeyFacebookClientID] = os.Getenv("FACEBOOK_CLIENT_ID")
} }
if constants.FACEBOOK_CLIENT_SECRET == "" { if envData.StringEnv[constants.EnvKeyFacebookClientSecret] == "" {
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET") envData.StringEnv[constants.EnvKeyFacebookClientSecret] = os.Getenv("FACEBOOK_CLIENT_SECRET")
} }
if constants.RESET_PASSWORD_URL == "" { if envData.StringEnv[constants.EnvKeyResetPasswordURL] == "" {
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/") envData.StringEnv[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
} }
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true" envData.BoolEnv[constants.EnvKeyDisableBasicAuthentication] = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true" envData.BoolEnv[constants.EnvKeyDisableEmailVerification] = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
constants.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true" envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
constants.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true" envData.BoolEnv[constants.EnvKeyDisableLoginPage] = os.Getenv("DISABLE_LOGIN_PAGE") == "true"
if constants.SMTP_HOST == "" || constants.SMTP_USERNAME == "" || constants.SMTP_PASSWORD == "" || constants.SENDER_EMAIL == "" { // no need to add nil check as its already done above
constants.DISABLE_EMAIL_VERIFICATION = true if envData.StringEnv[constants.EnvKeySmtpHost] == "" || envData.StringEnv[constants.EnvKeySmtpUsername] == "" || envData.StringEnv[constants.EnvKeySmtpPassword] == "" || envData.StringEnv[constants.EnvKeySenderEmail] == "" && envData.StringEnv[constants.EnvKeySmtpPort] == "" {
constants.DISABLE_MAGIC_LINK_LOGIN = true envData.BoolEnv[constants.EnvKeyDisableEmailVerification] = true
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true
}
if envData.BoolEnv[constants.EnvKeyDisableEmailVerification] {
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true
} }
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",") allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
@@ -209,19 +214,7 @@ func InitEnv() {
allowedOrigins = []string{"*"} allowedOrigins = []string{"*"}
} }
constants.ALLOWED_ORIGINS = allowedOrigins envData.SliceEnv[constants.EnvKeyAllowedOrigins] = 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
}
rolesEnv := strings.TrimSpace(os.Getenv("ROLES")) rolesEnv := strings.TrimSpace(os.Getenv("ROLES"))
rolesSplit := strings.Split(rolesEnv, ",") rolesSplit := strings.Split(rolesEnv, ",")
@@ -260,19 +253,21 @@ func InitEnv() {
} }
} }
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRoleSplit) > 0 { if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 {
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`) panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
} }
constants.ROLES = roles envData.SliceEnv[constants.EnvKeyRoles] = roles
constants.DEFAULT_ROLES = defaultRoles envData.SliceEnv[constants.EnvKeyDefaultRoles] = defaultRoles
constants.PROTECTED_ROLES = protectedRoles envData.SliceEnv[constants.EnvKeyProtectedRoles] = protectedRoles
if os.Getenv("ORGANIZATION_NAME") != "" { if os.Getenv("ORGANIZATION_NAME") != "" {
constants.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME") envData.StringEnv[constants.EnvKeyOrganizationName] = os.Getenv("ORGANIZATION_NAME")
} }
if os.Getenv("ORGANIZATION_LOGO") != "" { if os.Getenv("ORGANIZATION_LOGO") != "" {
constants.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO") envData.StringEnv[constants.EnvKeyOrganizationLogo] = os.Getenv("ORGANIZATION_LOGO")
} }
envstore.EnvInMemoryStoreObj.UpdateEnvStore(envData)
} }

145
server/env/persist_env.go vendored Normal file
View File

@@ -0,0 +1,145 @@
package env
import (
"encoding/json"
"log"
"os"
"strconv"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/google/uuid"
)
// PersistEnv persists the environment variables to the database
func PersistEnv() error {
env, err := db.Provider.GetEnv()
// 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]
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, hash)
encodedHash := utils.EncryptB64(hash)
configData, err := json.Marshal(envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
if err != nil {
return err
}
encryptedConfig, err := utils.EncryptAES(configData)
if err != nil {
return err
}
env = models.Env{
Hash: encodedHash,
EnvData: encryptedConfig,
}
db.Provider.AddEnv(env)
} else {
// decrypt the config data from db
// decryption can be done using the hash stored in db
encryptionKey := env.Hash
decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey)
if err != nil {
return err
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEncryptionKey, decryptedEncryptionKey)
decryptedConfigs, err := utils.DecryptAES(env.EnvData)
if err != nil {
return err
}
// temp store variable
var storeData envstore.Store
err = json.Unmarshal(decryptedConfigs, &storeData)
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 storeData.StringEnv {
if key != constants.EnvKeyEncryptionKey {
// 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 != "" {
if value != envValue {
storeData.StringEnv[key] = envValue
hasChanged = true
}
}
}
}
for key, value := range storeData.BoolEnv {
envValue := strings.TrimSpace(os.Getenv(key))
// env is not empty
if envValue != "" {
envValueBool, _ := strconv.ParseBool(envValue)
if value != envValueBool {
storeData.BoolEnv[key] = envValueBool
hasChanged = true
}
}
}
for key, value := range storeData.SliceEnv {
envValue := strings.TrimSpace(os.Getenv(key))
// env is not empty
if envValue != "" {
envStringArr := strings.Split(envValue, ",")
if !utils.IsStringArrayEqual(value, envStringArr) {
storeData.SliceEnv[key] = envStringArr
hasChanged = true
}
}
}
// handle derivative cases like disabling email verification & magic login
// in case SMTP is off but env is set to true
if storeData.StringEnv[constants.EnvKeySmtpHost] == "" || storeData.StringEnv[constants.EnvKeySmtpUsername] == "" || storeData.StringEnv[constants.EnvKeySmtpPassword] == "" || storeData.StringEnv[constants.EnvKeySenderEmail] == "" && storeData.StringEnv[constants.EnvKeySmtpPort] == "" {
if !storeData.BoolEnv[constants.EnvKeyDisableEmailVerification] {
storeData.BoolEnv[constants.EnvKeyDisableEmailVerification] = true
hasChanged = true
}
if !storeData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] {
storeData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = true
hasChanged = true
}
}
envstore.EnvInMemoryStoreObj.UpdateEnvStore(storeData)
if hasChanged {
encryptedConfig, err := utils.EncryptEnvData(storeData)
if err != nil {
return err
}
env.EnvData = encryptedConfig
_, err = db.Provider.UpdateEnv(env)
if err != nil {
log.Println("error updating config:", err)
return err
}
}
}
return nil
}

111
server/envstore/store.go Normal file
View File

@@ -0,0 +1,111 @@
package envstore
import (
"sync"
"github.com/authorizerdev/authorizer/server/constants"
)
var (
// ARG_DB_URL is the cli arg variable for the database url
ARG_DB_URL *string
// ARG_DB_TYPE is the cli arg variable for the database type
ARG_DB_TYPE *string
// ARG_ENV_FILE is the cli arg variable for the env file
ARG_ENV_FILE *string
)
// Store data structure
type Store struct {
StringEnv map[string]string `json:"string_env"`
BoolEnv map[string]bool `json:"bool_env"`
SliceEnv map[string][]string `json:"slice_env"`
}
// EnvInMemoryStore struct
type EnvInMemoryStore struct {
mutex sync.Mutex
store *Store
}
// EnvInMemoryStoreObj global variable for EnvInMemoryStore
var EnvInMemoryStoreObj = &EnvInMemoryStore{
store: &Store{
StringEnv: map[string]string{
constants.EnvKeyAdminCookieName: "authorizer-admin",
constants.EnvKeyJwtRoleClaim: "role",
constants.EnvKeyOrganizationName: "Authorizer",
constants.EnvKeyOrganizationLogo: "https://www.authorizer.io/images/logo.png",
},
BoolEnv: map[string]bool{
constants.EnvKeyDisableBasicAuthentication: false,
constants.EnvKeyDisableMagicLinkLogin: false,
constants.EnvKeyDisableEmailVerification: false,
constants.EnvKeyDisableLoginPage: false,
},
SliceEnv: map[string][]string{},
},
}
// UpdateEnvStore to update the whole env store object
func (e *EnvInMemoryStore) UpdateEnvStore(store Store) {
e.mutex.Lock()
defer e.mutex.Unlock()
// just override the keys + new keys
for key, value := range store.StringEnv {
e.store.StringEnv[key] = value
}
for key, value := range store.BoolEnv {
e.store.BoolEnv[key] = value
}
for key, value := range store.SliceEnv {
e.store.SliceEnv[key] = value
}
}
// UpdateEnvVariable to update the particular env variable
func (e *EnvInMemoryStore) UpdateEnvVariable(storeIdentifier, key string, value interface{}) {
e.mutex.Lock()
defer e.mutex.Unlock()
switch storeIdentifier {
case constants.StringStoreIdentifier:
e.store.StringEnv[key] = value.(string)
case constants.BoolStoreIdentifier:
e.store.BoolEnv[key] = value.(bool)
case constants.SliceStoreIdentifier:
e.store.SliceEnv[key] = value.([]string)
}
}
// GetStringStoreEnvVariable to get the env variable from string store object
func (e *EnvInMemoryStore) GetStringStoreEnvVariable(key string) string {
// e.mutex.Lock()
// defer e.mutex.Unlock()
return e.store.StringEnv[key]
}
// GetBoolStoreEnvVariable to get the env variable from bool store object
func (e *EnvInMemoryStore) GetBoolStoreEnvVariable(key string) bool {
// e.mutex.Lock()
// defer e.mutex.Unlock()
return e.store.BoolEnv[key]
}
// GetSliceStoreEnvVariable to get the env variable from slice store object
func (e *EnvInMemoryStore) GetSliceStoreEnvVariable(key string) []string {
// e.mutex.Lock()
// defer e.mutex.Unlock()
return e.store.SliceEnv[key]
}
// GetEnvStoreClone to get clone of current env store object
func (e *EnvInMemoryStore) GetEnvStoreClone() Store {
e.mutex.Lock()
defer e.mutex.Unlock()
result := *e.store
return result
}

View File

@@ -6,6 +6,7 @@ require (
github.com/99designs/gqlgen v0.14.0 github.com/99designs/gqlgen v0.14.0
github.com/arangodb/go-driver v1.2.1 github.com/arangodb/go-driver v1.2.1
github.com/coreos/go-oidc/v3 v3.1.0 github.com/coreos/go-oidc/v3 v3.1.0
github.com/fauna/faunadb-go/v5 v5.0.0-beta // indirect
github.com/gin-contrib/location v0.0.2 github.com/gin-contrib/location v0.0.2
github.com/gin-gonic/gin v1.7.2 github.com/gin-gonic/gin v1.7.2
github.com/go-playground/validator/v10 v10.8.0 // indirect github.com/go-playground/validator/v10 v10.8.0 // indirect
@@ -30,7 +31,7 @@ require (
google.golang.org/appengine v1.6.7 // indirect google.golang.org/appengine v1.6.7 // indirect
google.golang.org/protobuf v1.27.1 // indirect google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
gopkg.in/mail.v2 v2.3.1 // indirect gopkg.in/mail.v2 v2.3.1
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
gorm.io/driver/mysql v1.2.1 gorm.io/driver/mysql v1.2.1
gorm.io/driver/postgres v1.2.3 gorm.io/driver/postgres v1.2.3

View File

@@ -79,6 +79,8 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fauna/faunadb-go/v5 v5.0.0-beta h1:qjig7OPEsDPH/DJuHWIrOboreYd5aLQnSCzLgHDpnck=
github.com/fauna/faunadb-go/v5 v5.0.0-beta/go.mod h1:eoEA8JUERBnzK5/8Rxnetzx326ImTZ8c++wi2GQwrEU=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
@@ -456,6 +458,7 @@ golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,14 @@
package model package model
type AdminLoginInput struct {
AdminSecret string `json:"admin_secret"`
}
type AdminSignupInput struct {
AdminSecret string `json:"admin_secret"`
}
type AuthResponse struct { type AuthResponse struct {
Message string `json:"message"` Message string `json:"message"`
AccessToken *string `json:"access_token"` AccessToken *string `json:"access_token"`
@@ -13,6 +21,43 @@ type DeleteUserInput struct {
Email string `json:"email"` Email string `json:"email"`
} }
type Env struct {
AdminSecret *string `json:"ADMIN_SECRET"`
DatabaseName *string `json:"DATABASE_NAME"`
DatabaseURL *string `json:"DATABASE_URL"`
DatabaseType *string `json:"DATABASE_TYPE"`
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
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 Error struct { type Error struct {
Message string `json:"message"` Message string `json:"message"`
Reason string `json:"reason"` Reason string `json:"reason"`
@@ -22,6 +67,11 @@ type ForgotPasswordInput struct {
Email string `json:"email"` Email string `json:"email"`
} }
type IsValidJWTQueryInput struct {
Jwt *string `json:"jwt"`
Roles []string `json:"roles"`
}
type LoginInput struct { type LoginInput struct {
Email string `json:"email"` Email string `json:"email"`
Password string `json:"password"` Password string `json:"password"`
@@ -58,6 +108,10 @@ type Response struct {
Message string `json:"message"` Message string `json:"message"`
} }
type SessionQueryInput struct {
Roles []string `json:"roles"`
}
type SignUpInput struct { type SignUpInput struct {
Email string `json:"email"` Email string `json:"email"`
GivenName *string `json:"given_name"` GivenName *string `json:"given_name"`
@@ -73,6 +127,40 @@ type SignUpInput struct {
Roles []string `json:"roles"` Roles []string `json:"roles"`
} }
type UpdateEnvInput struct {
AdminSecret *string `json:"ADMIN_SECRET"`
CustomAccessTokenScript *string `json:"CUSTOM_ACCESS_TOKEN_SCRIPT"`
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
SMTPHost *string `json:"SMTP_HOST"`
SMTPPort *string `json:"SMTP_PORT"`
SenderEmail *string `json:"SENDER_EMAIL"`
SenderPassword *string `json:"SENDER_PASSWORD"`
JwtType *string `json:"JWT_TYPE"`
JwtSecret *string `json:"JWT_SECRET"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AuthorizerURL *string `json:"AUTHORIZER_URL"`
AppURL *string `json:"APP_URL"`
RedisURL *string `json:"REDIS_URL"`
CookieName *string `json:"COOKIE_NAME"`
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
Roles []string `json:"ROLES"`
ProtectedRoles []string `json:"PROTECTED_ROLES"`
DefaultRoles []string `json:"DEFAULT_ROLES"`
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}
type UpdateProfileInput struct { type UpdateProfileInput struct {
OldPassword *string `json:"old_password"` OldPassword *string `json:"old_password"`
NewPassword *string `json:"new_password"` NewPassword *string `json:"new_password"`
@@ -89,17 +177,18 @@ type UpdateProfileInput struct {
} }
type UpdateUserInput struct { type UpdateUserInput struct {
ID string `json:"id"` ID string `json:"id"`
Email *string `json:"email"` Email *string `json:"email"`
GivenName *string `json:"given_name"` EmailVerified *bool `json:"email_verified"`
FamilyName *string `json:"family_name"` GivenName *string `json:"given_name"`
MiddleName *string `json:"middle_name"` FamilyName *string `json:"family_name"`
Nickname *string `json:"nickname"` MiddleName *string `json:"middle_name"`
Gender *string `json:"gender"` Nickname *string `json:"nickname"`
Birthdate *string `json:"birthdate"` Gender *string `json:"gender"`
PhoneNumber *string `json:"phone_number"` Birthdate *string `json:"birthdate"`
Picture *string `json:"picture"` PhoneNumber *string `json:"phone_number"`
Roles []*string `json:"roles"` Picture *string `json:"picture"`
Roles []*string `json:"roles"`
} }
type User struct { type User struct {
@@ -122,6 +211,11 @@ type User struct {
UpdatedAt *int64 `json:"updated_at"` UpdatedAt *int64 `json:"updated_at"`
} }
type ValidJWTResponse struct {
Valid bool `json:"valid"`
Message string `json:"message"`
}
type VerificationRequest struct { type VerificationRequest struct {
ID string `json:"id"` ID string `json:"id"`
Identifier *string `json:"identifier"` Identifier *string `json:"identifier"`

View File

@@ -62,6 +62,90 @@ type Response {
message: String! message: String!
} }
type ValidJWTResponse {
valid: Boolean!
message: String!
}
type Env {
ADMIN_SECRET: String
DATABASE_NAME: String
DATABASE_URL: String
DATABASE_TYPE: String
CUSTOM_ACCESS_TOKEN_SCRIPT: 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 UpdateEnvInput {
ADMIN_SECRET: String
CUSTOM_ACCESS_TOKEN_SCRIPT: String
OLD_ADMIN_SECRET: String
SMTP_HOST: String
SMTP_PORT: String
SENDER_EMAIL: String
SENDER_PASSWORD: String
JWT_TYPE: String
JWT_SECRET: String
ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String
REDIS_URL: String
COOKIE_NAME: String
RESET_PASSWORD_URL: String
DISABLE_EMAIL_VERIFICATION: Boolean
DISABLE_BASIC_AUTHENTICATION: Boolean
DISABLE_MAGIC_LINK_LOGIN: Boolean
DISABLE_LOGIN_PAGE: Boolean
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
JWT_ROLE_CLAIM: String
GOOGLE_CLIENT_ID: String
GOOGLE_CLIENT_SECRET: String
GITHUB_CLIENT_ID: String
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
input AdminLoginInput {
admin_secret: String!
}
input AdminSignupInput {
admin_secret: String!
}
input SignUpInput { input SignUpInput {
email: String! email: String!
given_name: String given_name: String
@@ -110,6 +194,7 @@ input UpdateProfileInput {
input UpdateUserInput { input UpdateUserInput {
id: ID! id: ID!
email: String email: String
email_verified: Boolean
given_name: String given_name: String
family_name: String family_name: String
middle_name: String middle_name: String
@@ -140,6 +225,15 @@ input MagicLinkLoginInput {
roles: [String!] roles: [String!]
} }
input SessionQueryInput {
roles: [String!]
}
input IsValidJWTQueryInput {
jwt: String
roles: [String!]
}
type Mutation { type Mutation {
signup(params: SignUpInput!): AuthResponse! signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse! login(params: LoginInput!): AuthResponse!
@@ -153,13 +247,20 @@ type Mutation {
# admin only apis # admin only apis
_delete_user(params: DeleteUserInput!): Response! _delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User! _update_user(params: UpdateUserInput!): User!
_admin_signup(params: AdminSignupInput!): Response!
_admin_login(params: AdminLoginInput!): Response!
_admin_logout: Response!
_update_env(params: UpdateEnvInput!): Response!
} }
type Query { type Query {
meta: Meta! meta: Meta!
session(roles: [String!]): AuthResponse session(params: SessionQueryInput): AuthResponse!
is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse!
profile: User! profile: User!
# admin only apis # admin only apis
_users: [User!]! _users: [User!]!
_verification_requests: [VerificationRequest!]! _verification_requests: [VerificationRequest!]!
_admin_session: Response!
_env: Env!
} }

View File

@@ -12,67 +12,95 @@ import (
) )
func (r *mutationResolver) Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, error) { func (r *mutationResolver) Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, error) {
return resolvers.Signup(ctx, params) return resolvers.SignupResolver(ctx, params)
} }
func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error) { func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error) {
return resolvers.Login(ctx, params) return resolvers.LoginResolver(ctx, params)
} }
func (r *mutationResolver) MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) { func (r *mutationResolver) MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
return resolvers.MagicLinkLogin(ctx, params) return resolvers.MagicLinkLoginResolver(ctx, params)
} }
func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) { func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) {
return resolvers.Logout(ctx) return resolvers.LogoutResolver(ctx)
} }
func (r *mutationResolver) UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) { func (r *mutationResolver) UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) {
return resolvers.UpdateProfile(ctx, params) return resolvers.UpdateProfileResolver(ctx, params)
} }
func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) { func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) {
return resolvers.VerifyEmail(ctx, params) return resolvers.VerifyEmailResolver(ctx, params)
} }
func (r *mutationResolver) ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) { func (r *mutationResolver) ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) {
return resolvers.ResendVerifyEmail(ctx, params) return resolvers.ResendVerifyEmailResolver(ctx, params)
} }
func (r *mutationResolver) ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error) { func (r *mutationResolver) ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error) {
return resolvers.ForgotPassword(ctx, params) return resolvers.ForgotPasswordResolver(ctx, params)
} }
func (r *mutationResolver) ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { func (r *mutationResolver) ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
return resolvers.ResetPassword(ctx, params) return resolvers.ResetPasswordResolver(ctx, params)
} }
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) { func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
return resolvers.DeleteUser(ctx, params) return resolvers.DeleteUserResolver(ctx, params)
} }
func (r *mutationResolver) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) { func (r *mutationResolver) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) {
return resolvers.UpdateUser(ctx, params) return resolvers.UpdateUserResolver(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.AdminLogoutResolver(ctx)
}
func (r *mutationResolver) UpdateEnv(ctx context.Context, params model.UpdateEnvInput) (*model.Response, error) {
return resolvers.UpdateEnvResolver(ctx, params)
} }
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) { func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
return resolvers.Meta(ctx) return resolvers.MetaResolver(ctx)
} }
func (r *queryResolver) Session(ctx context.Context, roles []string) (*model.AuthResponse, error) { func (r *queryResolver) Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) {
return resolvers.Session(ctx, roles) return resolvers.SessionResolver(ctx, params)
}
func (r *queryResolver) IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) {
return resolvers.IsValidJwtResolver(ctx, params)
} }
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) { func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
return resolvers.Profile(ctx) return resolvers.ProfileResolver(ctx)
} }
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) { func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
return resolvers.Users(ctx) return resolvers.UsersResolver(ctx)
} }
func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) { func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) {
return resolvers.VerificationRequests(ctx) return resolvers.VerificationRequestsResolver(ctx)
}
func (r *queryResolver) AdminSession(ctx context.Context) (*model.Response, error) {
return resolvers.AdminSessionResolver(ctx)
}
func (r *queryResolver) Env(ctx context.Context) (*model.Env, error) {
return resolvers.EnvResolver(ctx)
} }
// Mutation returns generated.MutationResolver implementation. // Mutation returns generated.MutationResolver implementation.
@@ -81,7 +109,5 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol
// Query returns generated.QueryResolver implementation. // Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type ( type mutationResolver struct{ *Resolver }
mutationResolver struct{ *Resolver } type queryResolver struct{ *Resolver }
queryResolver struct{ *Resolver }
)

View File

@@ -1,22 +1,25 @@
package handlers package handlers
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"log" "log"
"net/http" "net/http"
"strings" "strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// State is the struct that holds authorizer url and redirect url
// They are provided via query string in the request
type State struct { type State struct {
AuthorizerURL string `json:"authorizerURL"` AuthorizerURL string `json:"authorizerURL"`
RedirectURL string `json:"redirectURL"` RedirectURL string `json:"redirectURL"`
} }
// AppHandler is the handler for the /app route
func AppHandler() gin.HandlerFunc { func AppHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
state := c.Query("state") state := c.Query("state")
@@ -24,23 +27,17 @@ func AppHandler() gin.HandlerFunc {
var stateObj State var stateObj State
if state == "" { if state == "" {
// cookie, err := utils.GetAuthToken(c) stateObj.AuthorizerURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)
// if err != nil { stateObj.RedirectURL = stateObj.AuthorizerURL + "/app"
// c.JSON(400, gin.H{"error": "invalid state"})
// return
// }
stateObj.AuthorizerURL = constants.AUTHORIZER_URL
stateObj.RedirectURL = constants.AUTHORIZER_URL + "/app"
} else { } else {
decodedState, err := base64.StdEncoding.DecodeString(state) decodedState, err := utils.DecryptB64(state)
if err != nil { if err != nil {
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"}) c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
return return
} }
err = json.Unmarshal(decodedState, &stateObj) err = json.Unmarshal([]byte(decodedState), &stateObj)
if err != nil { if err != nil {
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"}) c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
return return
@@ -60,7 +57,7 @@ func AppHandler() gin.HandlerFunc {
} }
// validate host and domain of authorizer url // validate host and domain of authorizer url
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.AUTHORIZER_URL { if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) {
c.JSON(400, gin.H{"error": "invalid host url"}) c.JSON(400, gin.H{"error": "invalid host url"})
return return
} }
@@ -77,8 +74,8 @@ func AppHandler() gin.HandlerFunc {
"data": map[string]string{ "data": map[string]string{
"authorizerURL": stateObj.AuthorizerURL, "authorizerURL": stateObj.AuthorizerURL,
"redirectURL": stateObj.RedirectURL, "redirectURL": stateObj.RedirectURL,
"organizationName": constants.ORGANIZATION_NAME, "organizationName": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
"organizationLogo": constants.ORGANIZATION_LOGO, "organizationLogo": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo),
}, },
}) })
} }

View File

@@ -0,0 +1,26 @@
package handlers
import (
"net/http"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/gin-gonic/gin"
)
// DashboardHandler is the handler for the /dashboard route
func DashboardHandler() gin.HandlerFunc {
return func(c *gin.Context) {
isOnboardingCompleted := false
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret) != "" {
isOnboardingCompleted = true
}
c.HTML(http.StatusOK, "dashboard.tmpl", gin.H{
"data": map[string]interface{}{
"isOnboardingCompleted": isOnboardingCompleted,
},
})
}
}

View File

@@ -7,7 +7,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// Defining the Graphql handler // GraphqlHandler is the main handler that handels all the graphql requests
func GraphqlHandler() gin.HandlerFunc { func GraphqlHandler() gin.HandlerFunc {
// NewExecutableSchema and Config are in the generated.go file // NewExecutableSchema and Config are in the generated.go file
// Resolver is in the resolver.go file // Resolver is in the resolver.go file

View File

@@ -11,18 +11,144 @@ import (
"time" "time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/oauth"
"github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/coreos/go-oidc/v3/oidc" "github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
func processGoogleUserInfo(code string) (db.User, error) { // OAuthCallbackHandler handles the OAuth callback for various oauth providers
user := db.User{} func OAuthCallbackHandler() gin.HandlerFunc {
return func(c *gin.Context) {
provider := c.Param("oauth_provider")
state := c.Request.FormValue("state")
sessionState := sessionstore.GetSocailLoginState(state)
if sessionState == "" {
c.JSON(400, gin.H{"error": "invalid oauth state"})
}
sessionstore.RemoveSocialLoginState(state)
// contains random token, redirect url, role
sessionSplit := strings.Split(state, "___")
// TODO validate redirect url
if len(sessionSplit) < 2 {
c.JSON(400, gin.H{"error": "invalid redirect url"})
return
}
inputRoles := strings.Split(sessionSplit[2], ",")
redirectURL := sessionSplit[1]
var err error
user := models.User{}
code := c.Request.FormValue("code")
switch provider {
case constants.SignupMethodGoogle:
user, err = processGoogleUserInfo(code)
case constants.SignupMethodGithub:
user, err = processGithubUserInfo(code)
case constants.SignupMethodFacebook:
user, err = processFacebookUserInfo(code)
default:
err = fmt.Errorf(`invalid oauth provider`)
}
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
existingUser, err := db.Provider.GetUserByEmail(user.Email)
if err != nil {
// user not registered, register user and generate session token
user.SignupMethods = provider
// make sure inputRoles don't include protected roles
hasProtectedRole := false
for _, ir := range inputRoles {
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ir) {
hasProtectedRole = true
}
}
if hasProtectedRole {
c.JSON(400, gin.H{"error": "invalid role"})
return
}
user.Roles = strings.Join(inputRoles, ",")
now := time.Now().Unix()
user.EmailVerifiedAt = &now
user, _ = db.Provider.AddUser(user)
} else {
// user exists in db, check if method was google
// if not append google to existing signup method and save it
signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider
}
user.SignupMethods = signupMethod
user.Password = existingUser.Password
// There multiple scenarios with roles here in social login
// 1. user has access to protected roles + roles and trying to login
// 2. user has not signed up for one of the available role but trying to signup.
// Need to modify roles in this case
// find the unassigned roles
existingRoles := strings.Split(existingUser.Roles, ",")
unasignedRoles := []string{}
for _, ir := range inputRoles {
if !utils.StringSliceContains(existingRoles, ir) {
unasignedRoles = append(unasignedRoles, ir)
}
}
if len(unasignedRoles) > 0 {
// check if it contains protected unassigned role
hasProtectedRole := false
for _, ur := range unasignedRoles {
if utils.StringSliceContains(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles), ur) {
hasProtectedRole = true
}
}
if hasProtectedRole {
c.JSON(400, gin.H{"error": "invalid role"})
return
} else {
user.Roles = existingUser.Roles + "," + strings.Join(unasignedRoles, ",")
}
} else {
user.Roles = existingUser.Roles
}
user.Key = existingUser.Key
user.ID = existingUser.ID
user, err = db.Provider.UpdateUser(user)
}
user, _ = db.Provider.GetUserByEmail(user.Email)
authToken, _ := token.CreateAuthToken(user, inputRoles)
sessionstore.SetUserSession(user.ID, authToken.FingerPrint, authToken.RefreshToken.Token)
cookie.SetCookie(c, authToken.AccessToken.Token, authToken.RefreshToken.Token, authToken.FingerPrintHash)
utils.SaveSessionInDB(user.ID, c)
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}
}
func processGoogleUserInfo(code string) (models.User, error) {
user := models.User{}
ctx := context.Background() ctx := context.Background()
oauth2Token, err := oauth.OAuthProviders.GoogleConfig.Exchange(ctx, code) oauth2Token, err := oauth.OAuthProviders.GoogleConfig.Exchange(ctx, code)
if err != nil { if err != nil {
@@ -50,8 +176,8 @@ func processGoogleUserInfo(code string) (db.User, error) {
return user, nil return user, nil
} }
func processGithubUserInfo(code string) (db.User, error) { func processGithubUserInfo(code string) (models.User, error) {
user := db.User{} user := models.User{}
token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code) token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
if err != nil { if err != nil {
return user, fmt.Errorf("invalid github exchange code: %s", err.Error()) return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
@@ -91,7 +217,7 @@ func processGithubUserInfo(code string) (db.User, error) {
picture := userRawData["avatar_url"] picture := userRawData["avatar_url"]
user = db.User{ user = models.User{
GivenName: &firstName, GivenName: &firstName,
FamilyName: &lastName, FamilyName: &lastName,
Picture: &picture, Picture: &picture,
@@ -101,8 +227,8 @@ func processGithubUserInfo(code string) (db.User, error) {
return user, nil return user, nil
} }
func processFacebookUserInfo(code string) (db.User, error) { func processFacebookUserInfo(code string) (models.User, error) {
user := db.User{} user := models.User{}
token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code) token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
if err != nil { if err != nil {
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error()) return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
@@ -136,7 +262,7 @@ func processFacebookUserInfo(code string) (db.User, error) {
lastName := fmt.Sprintf("%v", userRawData["last_name"]) lastName := fmt.Sprintf("%v", userRawData["last_name"])
picture := fmt.Sprintf("%v", picDataObject["url"]) picture := fmt.Sprintf("%v", picDataObject["url"])
user = db.User{ user = models.User{
GivenName: &firstName, GivenName: &firstName,
FamilyName: &lastName, FamilyName: &lastName,
Picture: &picture, Picture: &picture,
@@ -145,127 +271,3 @@ func processFacebookUserInfo(code string) (db.User, error) {
return user, nil return user, nil
} }
func OAuthCallbackHandler() gin.HandlerFunc {
return func(c *gin.Context) {
provider := c.Param("oauth_provider")
state := c.Request.FormValue("state")
sessionState := session.GetSocailLoginState(state)
if sessionState == "" {
c.JSON(400, gin.H{"error": "invalid oauth state"})
}
session.RemoveSocialLoginState(state)
// contains random token, redirect url, role
sessionSplit := strings.Split(state, "___")
// TODO validate redirect url
if len(sessionSplit) < 2 {
c.JSON(400, gin.H{"error": "invalid redirect url"})
return
}
inputRoles := strings.Split(sessionSplit[2], ",")
redirectURL := sessionSplit[1]
var err error
user := db.User{}
code := c.Request.FormValue("code")
switch provider {
case enum.Google.String():
user, err = processGoogleUserInfo(code)
case enum.Github.String():
user, err = processGithubUserInfo(code)
case enum.Facebook.String():
user, err = processFacebookUserInfo(code)
default:
err = fmt.Errorf(`invalid oauth provider`)
}
if err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
existingUser, err := db.Mgr.GetUserByEmail(user.Email)
if err != nil {
// user not registered, register user and generate session token
user.SignupMethods = provider
// make sure inputRoles don't include protected roles
hasProtectedRole := false
for _, ir := range inputRoles {
if utils.StringSliceContains(constants.PROTECTED_ROLES, ir) {
hasProtectedRole = true
}
}
if hasProtectedRole {
c.JSON(400, gin.H{"error": "invalid role"})
return
}
user.Roles = strings.Join(inputRoles, ",")
now := time.Now().Unix()
user.EmailVerifiedAt = &now
user, _ = db.Mgr.AddUser(user)
} else {
// user exists in db, check if method was google
// if not append google to existing signup method and save it
signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider
}
user.SignupMethods = signupMethod
user.Password = existingUser.Password
// There multiple scenarios with roles here in social login
// 1. user has access to protected roles + roles and trying to login
// 2. user has not signed up for one of the available role but trying to signup.
// Need to modify roles in this case
// find the unassigned roles
existingRoles := strings.Split(existingUser.Roles, ",")
unasignedRoles := []string{}
for _, ir := range inputRoles {
if !utils.StringSliceContains(existingRoles, ir) {
unasignedRoles = append(unasignedRoles, ir)
}
}
if len(unasignedRoles) > 0 {
// check if it contains protected unassigned role
hasProtectedRole := false
for _, ur := range unasignedRoles {
if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) {
hasProtectedRole = true
}
}
if hasProtectedRole {
c.JSON(400, gin.H{"error": "invalid role"})
return
} else {
user.Roles = existingUser.Roles + "," + strings.Join(unasignedRoles, ",")
}
} else {
user.Roles = existingUser.Roles
}
user.Key = existingUser.Key
user.ID = existingUser.ID
user, err = db.Mgr.UpdateUser(user)
}
user, _ = db.Mgr.GetUserByEmail(user.Email)
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, inputRoles)
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, inputRoles)
utils.SetCookie(c, accessToken)
session.SetToken(userIdStr, accessToken, refreshToken)
utils.CreateSession(user.ID, c)
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
}
}

View File

@@ -5,16 +5,15 @@ import (
"strings" "strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/oauth"
"github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/google/uuid" "github.com/google/uuid"
) )
// set host in the oauth state that is useful for redirecting // OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
func OAuthLoginHandler() gin.HandlerFunc { func OAuthLoginHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
// TODO validate redirect URL // TODO validate redirect URL
@@ -34,14 +33,14 @@ func OAuthLoginHandler() gin.HandlerFunc {
// use protected roles verification for admin login only. // use protected roles verification for admin login only.
// though if not associated with user, it will be rejected from oauth_callback // though if not associated with user, it will be rejected from oauth_callback
if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), rolesSplit) { if !utils.IsValidRoles(append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"error": "invalid role", "error": "invalid role",
}) })
return return
} }
} else { } else {
roles = strings.Join(constants.DEFAULT_ROLES, ",") roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
} }
uuid := uuid.New() uuid := uuid.New()
@@ -50,32 +49,32 @@ func OAuthLoginHandler() gin.HandlerFunc {
provider := c.Param("oauth_provider") provider := c.Param("oauth_provider")
isProviderConfigured := true isProviderConfigured := true
switch provider { switch provider {
case enum.Google.String(): case constants.SignupMethodGoogle:
if oauth.OAuthProviders.GoogleConfig == nil { if oauth.OAuthProviders.GoogleConfig == nil {
isProviderConfigured = false isProviderConfigured = false
break break
} }
session.SetSocailLoginState(oauthStateString, enum.Google.String()) sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle)
// during the init of OAuthProvider authorizer url might be empty // during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google" oauth.OAuthProviders.GoogleConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/google"
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Github.String(): case constants.SignupMethodGithub:
if oauth.OAuthProviders.GithubConfig == nil { if oauth.OAuthProviders.GithubConfig == nil {
isProviderConfigured = false isProviderConfigured = false
break break
} }
session.SetSocailLoginState(oauthStateString, enum.Github.String()) sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub)
oauth.OAuthProviders.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github" oauth.OAuthProviders.GithubConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/github"
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Facebook.String(): case constants.SignupMethodFacebook:
if oauth.OAuthProviders.FacebookConfig == nil { if oauth.OAuthProviders.FacebookConfig == nil {
isProviderConfigured = false isProviderConfigured = false
break break
} }
session.SetSocailLoginState(oauthStateString, enum.Facebook.String()) sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook)
oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook" oauth.OAuthProviders.FacebookConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/facebook"
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
default: default:

Some files were not shown because too many files have changed in this diff Show More