Compare commits
48 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
e170569959 | ||
![]() |
b42cc1549a | ||
![]() |
4bc9059b0f | ||
![]() |
87b1cac979 | ||
![]() |
7f18a3f634 | ||
![]() |
0511e737ae | ||
![]() |
003d88fb6c | ||
![]() |
515b72f484 | ||
![]() |
cb96d2d8d1 | ||
![]() |
8a4b2feffe | ||
![]() |
13c038effd | ||
![]() |
38419a4ef4 | ||
![]() |
7785f98dcd | ||
![]() |
5ecc49f861 | ||
![]() |
3cb02dd62c | ||
![]() |
ddda237178 | ||
![]() |
3b4d0d9769 | ||
![]() |
c15b65b473 | ||
![]() |
e07448f670 | ||
![]() |
a596d91ce0 | ||
![]() |
f1b4141367 | ||
![]() |
7ce96367a3 | ||
![]() |
974622b9be | ||
![]() |
8bee841d66 | ||
![]() |
9363d83945 | ||
![]() |
75affcbf30 | ||
![]() |
f5aeda1283 | ||
![]() |
3d75a4a281 | ||
![]() |
f9ed91934e | ||
![]() |
266b9e34b6 | ||
![]() |
047e867faa | ||
![]() |
9d08d6c672 | ||
![]() |
91c35aa381 | ||
![]() |
8e85d0ddbd | ||
![]() |
bb4052d1d2 | ||
![]() |
e0ae6aa2e0 | ||
![]() |
ca716ec1dd | ||
![]() |
818790650a | ||
![]() |
eb5041008d | ||
![]() |
152ab6dfd5 | ||
![]() |
192070c18e | ||
![]() |
f7f1a3e4b3 | ||
![]() |
9c8e9baa39 | ||
![]() |
217410e9a4 | ||
![]() |
e35d0cbcd6 | ||
![]() |
d9c40057e6 | ||
![]() |
86bcb8ca87 | ||
![]() |
cf4d94a7aa |
233
.github/CONTRIBUTING.md
vendored
233
.github/CONTRIBUTING.md
vendored
@@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
7
.github/workflows/release.yaml
vendored
7
.github/workflows/release.yaml
vendored
@@ -23,7 +23,8 @@ jobs:
|
|||||||
sudo mv github-assets-uploader /usr/sbin/ && \
|
sudo mv github-assets-uploader /usr/sbin/ && \
|
||||||
sudo rm -f github-assets-uploader.tar.gz && \
|
sudo rm -f github-assets-uploader.tar.gz && \
|
||||||
github-assets-uploader -version && \
|
github-assets-uploader -version && \
|
||||||
make build-app
|
make build-app && \
|
||||||
|
make build-dashboard
|
||||||
- name: Print Go paths
|
- name: Print Go paths
|
||||||
run: whereis go
|
run: whereis go
|
||||||
- name: Print Go Version
|
- name: Print Go Version
|
||||||
@@ -37,12 +38,12 @@ jobs:
|
|||||||
make clean && \
|
make clean && \
|
||||||
CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
|
CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
|
||||||
mv build/server build/server.exe && \
|
mv build/server build/server.exe && \
|
||||||
zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates
|
zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates dashboard/build
|
||||||
- name: Package files for linux
|
- name: Package files for linux
|
||||||
run: |
|
run: |
|
||||||
make clean && \
|
make clean && \
|
||||||
CGO_ENABLED=1 make && \
|
CGO_ENABLED=1 make && \
|
||||||
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates
|
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates dashboard/build
|
||||||
- name: Upload assets
|
- name: Upload assets
|
||||||
run: |
|
run: |
|
||||||
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \
|
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \
|
||||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,6 +3,8 @@ server/.env
|
|||||||
data
|
data
|
||||||
app/node_modules
|
app/node_modules
|
||||||
app/build
|
app/build
|
||||||
|
dashboard/node_modules
|
||||||
|
dashboard/build
|
||||||
build
|
build
|
||||||
.env
|
.env
|
||||||
data.db
|
data.db
|
||||||
|
@@ -14,14 +14,17 @@ RUN apk add build-base &&\
|
|||||||
FROM node:17-alpine3.12 as node-builder
|
FROM node:17-alpine3.12 as node-builder
|
||||||
WORKDIR /authorizer
|
WORKDIR /authorizer
|
||||||
COPY app app
|
COPY app app
|
||||||
|
COPY dashboard dashboard
|
||||||
COPY Makefile .
|
COPY Makefile .
|
||||||
RUN apk add build-base &&\
|
RUN apk add build-base &&\
|
||||||
make build-app
|
make build-app && \
|
||||||
|
make build-dashboard
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM alpine:latest
|
||||||
WORKDIR /root/
|
WORKDIR /root/
|
||||||
RUN mkdir app
|
RUN mkdir app dashboard
|
||||||
COPY --from=node-builder /authorizer/app/build app/build
|
COPY --from=node-builder /authorizer/app/build app/build
|
||||||
|
COPY --from=node-builder /authorizer/dashboard/build dashboard/build
|
||||||
COPY --from=go-builder /authorizer/build build
|
COPY --from=go-builder /authorizer/build build
|
||||||
COPY templates templates
|
COPY templates templates
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
4
Makefile
4
Makefile
@@ -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
11
TODO.md
@@ -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
|
||||||
|
@@ -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
58
app/package-lock.json
generated
@@ -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"
|
||||||
}
|
}
|
||||||
|
@@ -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
12
dashboard/README.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Authorizer dashboard
|
||||||
|
|
||||||
|
### Getting started
|
||||||
|
|
||||||
|
**Setting up locally**
|
||||||
|
|
||||||
|
- `cd dashboard`
|
||||||
|
- `npm start`
|
||||||
|
|
||||||
|
**Creating production build**
|
||||||
|
|
||||||
|
- `make build-dashboard`
|
12
dashboard/esbuild.config.js
Normal file
12
dashboard/esbuild.config.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
const __is_prod__ = process.env.NODE_ENV === 'production';
|
||||||
|
require('esbuild').build({
|
||||||
|
entryPoints: ['src/index.tsx'],
|
||||||
|
chunkNames: '[name]-[hash]',
|
||||||
|
bundle: true,
|
||||||
|
minify: __is_prod__,
|
||||||
|
outdir: 'build',
|
||||||
|
splitting: true,
|
||||||
|
format: 'esm',
|
||||||
|
watch: !__is_prod__,
|
||||||
|
logLevel: 'info',
|
||||||
|
});
|
1682
dashboard/package-lock.json
generated
Normal file
1682
dashboard/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
dashboard/package.json
Normal file
30
dashboard/package.json
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
{
|
||||||
|
"name": "dashboard",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
||||||
|
"start": "NODE_ENV=development node ./esbuild.config.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "Lakhan Samani",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@chakra-ui/react": "^1.7.3",
|
||||||
|
"@emotion/react": "^11.7.1",
|
||||||
|
"@emotion/styled": "^11.6.0",
|
||||||
|
"@types/react": "^17.0.38",
|
||||||
|
"@types/react-dom": "^17.0.11",
|
||||||
|
"@types/react-router-dom": "^5.3.2",
|
||||||
|
"esbuild": "^0.14.9",
|
||||||
|
"framer-motion": "^5.5.5",
|
||||||
|
"graphql": "^16.2.0",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"react-icons": "^4.3.1",
|
||||||
|
"react-router-dom": "^6.2.1",
|
||||||
|
"typescript": "^4.5.4",
|
||||||
|
"urql": "^2.0.6"
|
||||||
|
}
|
||||||
|
}
|
45
dashboard/src/App.tsx
Normal file
45
dashboard/src/App.tsx
Normal 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
0
dashboard/src/Router.tsx
Normal file
215
dashboard/src/components/Menu.tsx
Normal file
215
dashboard/src/components/Menu.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
1
dashboard/src/constants.ts
Normal file
1
dashboard/src/constants.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export const LOGO_URL = "https://user-images.githubusercontent.com/6964334/147834043-fc384cab-e7ca-40f8-9663-38fc25fd5f3a.png"
|
48
dashboard/src/contexts/AuthContext.tsx
Normal file
48
dashboard/src/contexts/AuthContext.tsx
Normal 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);
|
23
dashboard/src/graphql/mutation/index.ts
Normal file
23
dashboard/src/graphql/mutation/index.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
7
dashboard/src/graphql/queries/index.ts
Normal file
7
dashboard/src/graphql/queries/index.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export const AdminSessionQuery = `
|
||||||
|
query {
|
||||||
|
_admin_session{
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
5
dashboard/src/index.tsx
Normal file
5
dashboard/src/index.tsx
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
ReactDOM.render(<App />, document.getElementById('root'));
|
31
dashboard/src/layouts/AuthLayout.tsx
Normal file
31
dashboard/src/layouts/AuthLayout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
39
dashboard/src/layouts/DashboardLayout.tsx
Normal file
39
dashboard/src/layouts/DashboardLayout.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
124
dashboard/src/pages/Auth.tsx
Normal file
124
dashboard/src/pages/Auth.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
35
dashboard/src/pages/Environment.tsx
Normal file
35
dashboard/src/pages/Environment.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
18
dashboard/src/pages/Home.tsx
Normal file
18
dashboard/src/pages/Home.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
6
dashboard/src/pages/Users.tsx
Normal file
6
dashboard/src/pages/Users.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { Box } from '@chakra-ui/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
export default function Users() {
|
||||||
|
return <Box>Welcome to Users Page</Box>;
|
||||||
|
}
|
41
dashboard/src/routes/index.tsx
Normal file
41
dashboard/src/routes/index.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
};
|
6
dashboard/src/utils/index.ts
Normal file
6
dashboard/src/utils/index.ts
Normal 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
72
dashboard/tsconfig.json
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig.json to read more about this file */
|
||||||
|
|
||||||
|
/* Basic Options */
|
||||||
|
// "incremental": true, /* Enable incremental compilation */
|
||||||
|
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */,
|
||||||
|
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */,
|
||||||
|
// "lib": ["es2018", "dom"], /* Specify library files to be included in the compilation. */
|
||||||
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
|
"jsx": "react" /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */,
|
||||||
|
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
// "outDir": "./", /* Redirect output structure to the directory. */
|
||||||
|
"rootDir": "src" /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */,
|
||||||
|
// "composite": true, /* Enable project compilation */
|
||||||
|
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
|
||||||
|
// "removeComments": true, /* Do not emit comments to output. */
|
||||||
|
// "noEmit": true, /* Do not emit outputs. */
|
||||||
|
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
|
||||||
|
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
|
||||||
|
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
|
||||||
|
|
||||||
|
/* Strict Type-Checking Options */
|
||||||
|
"strict": true /* Enable all strict type-checking options. */,
|
||||||
|
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* Enable strict null checks. */
|
||||||
|
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
|
||||||
|
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
|
||||||
|
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
|
||||||
|
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
|
||||||
|
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||||
|
|
||||||
|
/* Additional Checks */
|
||||||
|
// "noUnusedLocals": true, /* Report errors on unused locals. */
|
||||||
|
// "noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||||
|
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */
|
||||||
|
|
||||||
|
/* Module Resolution Options */
|
||||||
|
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
|
||||||
|
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
|
||||||
|
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
|
||||||
|
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
|
||||||
|
// "typeRoots": [], /* List of folders to include type definitions from. */
|
||||||
|
// "types": [], /* Type declaration files to be included in compilation. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
|
||||||
|
"esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
|
||||||
|
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
|
||||||
|
/* Source Map Options */
|
||||||
|
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
|
||||||
|
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
|
||||||
|
|
||||||
|
/* Experimental Options */
|
||||||
|
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||||
|
|
||||||
|
/* Advanced Options */
|
||||||
|
"skipLibCheck": true /* Skip type checking of declaration files. */,
|
||||||
|
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
|
||||||
|
}
|
||||||
|
}
|
@@ -1,7 +1,7 @@
|
|||||||
VERSION="$1"
|
VERSION="$1"
|
||||||
make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make
|
make clean && make build-app && CGO_ENABLED=1 VERSION=${VERSION} make
|
||||||
FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz
|
FILE_NAME=authorizer-${VERSION}-darwin-amd64.tar.gz
|
||||||
tar cvfz ${FILE_NAME} .env app/build build templates
|
tar cvfz ${FILE_NAME} .env app/build build templates dashboard/build
|
||||||
AUTH="Authorization: token $GITHUB_TOKEN"
|
AUTH="Authorization: token $GITHUB_TOKEN"
|
||||||
RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION})
|
RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION})
|
||||||
echo $RELASE_INFO
|
echo $RELASE_INFO
|
||||||
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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{"*"})
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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)
|
|
||||||
})
|
|
||||||
}
|
|
@@ -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"
|
|
||||||
)
|
|
18
server/constants/db_types.go
Normal file
18
server/constants/db_types.go
Normal 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
95
server/constants/env.go
Normal 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"
|
||||||
|
)
|
@@ -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"
|
||||||
|
14
server/constants/signup_methods.go
Normal file
14
server/constants/signup_methods.go
Normal 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"
|
||||||
|
)
|
8
server/constants/token_types.go
Normal file
8
server/constants/token_types.go
Normal 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"
|
||||||
|
)
|
12
server/constants/verification_types.go
Normal file
12
server/constants/verification_types.go
Normal 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"
|
||||||
|
)
|
44
server/cookie/admin_cookie.go
Normal file
44
server/cookie/admin_cookie.go
Normal 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
101
server/cookie/cookie.go
Normal 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)
|
||||||
|
}
|
@@ -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
|
|
||||||
}
|
|
138
server/db/db.go
138
server/db/db.go
@@ -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
11
server/db/models/env.go
Normal 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
21
server/db/models/model.go
Normal 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",
|
||||||
|
}
|
||||||
|
)
|
13
server/db/models/session.go
Normal file
13
server/db/models/session.go
Normal 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
54
server/db/models/user.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
13
server/db/models/verification_requests.go
Normal file
13
server/db/models/verification_requests.go
Normal 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"`
|
||||||
|
}
|
123
server/db/providers/arangodb/arangodb.go
Normal file
123
server/db/providers/arangodb/arangodb.go
Normal 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
|
||||||
|
}
|
73
server/db/providers/arangodb/env.go
Normal file
73
server/db/providers/arangodb/env.go
Normal 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
|
||||||
|
}
|
42
server/db/providers/arangodb/session.go
Normal file
42
server/db/providers/arangodb/session.go
Normal 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
|
||||||
|
}
|
157
server/db/providers/arangodb/user.go
Normal file
157
server/db/providers/arangodb/user.go
Normal 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
|
||||||
|
}
|
135
server/db/providers/arangodb/verification_requests.go
Normal file
135
server/db/providers/arangodb/verification_requests.go
Normal 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
|
||||||
|
}
|
51
server/db/providers/faunadb/env.go
Normal file
51
server/db/providers/faunadb/env.go
Normal 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
|
||||||
|
}
|
164
server/db/providers/faunadb/faunadb.go
Normal file
164
server/db/providers/faunadb/faunadb.go
Normal 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
|
||||||
|
}
|
25
server/db/providers/faunadb/session.go
Normal file
25
server/db/providers/faunadb/session.go
Normal 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
|
||||||
|
}
|
60
server/db/providers/faunadb/user.go
Normal file
60
server/db/providers/faunadb/user.go
Normal 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
|
||||||
|
}
|
46
server/db/providers/faunadb/verification_requests.go
Normal file
46
server/db/providers/faunadb/verification_requests.go
Normal 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
|
||||||
|
}
|
66
server/db/providers/mongodb/env.go
Normal file
66
server/db/providers/mongodb/env.go
Normal 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
|
||||||
|
}
|
@@ -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
|
||||||
}
|
}
|
40
server/db/providers/mongodb/session.go
Normal file
40
server/db/providers/mongodb/session.go
Normal 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
|
||||||
|
}
|
108
server/db/providers/mongodb/user.go
Normal file
108
server/db/providers/mongodb/user.go
Normal 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
|
||||||
|
}
|
91
server/db/providers/mongodb/verification_requests.go
Normal file
91
server/db/providers/mongodb/verification_requests.go
Normal 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
|
||||||
|
}
|
41
server/db/providers/providers.go
Normal file
41
server/db/providers/providers.go
Normal 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)
|
||||||
|
}
|
49
server/db/providers/sql/env.go
Normal file
49
server/db/providers/sql/env.go
Normal 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
|
||||||
|
}
|
38
server/db/providers/sql/session.go
Normal file
38
server/db/providers/sql/session.go
Normal 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
|
||||||
|
}
|
53
server/db/providers/sql/sql.go
Normal file
53
server/db/providers/sql/sql.go
Normal 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
|
||||||
|
}
|
101
server/db/providers/sql/user.go
Normal file
101
server/db/providers/sql/user.go
Normal 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
|
||||||
|
}
|
80
server/db/providers/sql/verification_requests.go
Normal file
80
server/db/providers/sql/verification_requests.go
Normal 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
|
||||||
|
}
|
@@ -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
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
@@ -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
|
|
||||||
}
|
|
@@ -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 {
|
||||||
|
112
server/email/forgot_password_email.go
Normal file
112
server/email/forgot_password_email.go
Normal 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)
|
||||||
|
}
|
107
server/email/verification_email.go
Normal file
107
server/email/verification_email.go
Normal 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)
|
||||||
|
}
|
@@ -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]
|
|
||||||
}
|
|
@@ -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]
|
|
||||||
}
|
|
@@ -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]
|
|
||||||
}
|
|
@@ -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]
|
|
||||||
}
|
|
@@ -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
247
server/env/env.go
vendored
@@ -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
145
server/env/persist_env.go
vendored
Normal 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
111
server/envstore/store.go
Normal 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
|
||||||
|
}
|
@@ -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
|
||||||
|
@@ -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
@@ -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"`
|
||||||
|
@@ -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!
|
||||||
}
|
}
|
||||||
|
@@ -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 }
|
|
||||||
)
|
|
||||||
|
@@ -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),
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
26
server/handlers/dashboard.go
Normal file
26
server/handlers/dashboard.go
Normal 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,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
|
@@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@@ -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
Reference in New Issue
Block a user