Compare commits
88 Commits
0.4.0
...
fix/organi
Author | SHA1 | Date | |
---|---|---|---|
![]() |
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 | ||
![]() |
2d819f5d3c | ||
![]() |
3221740198 | ||
![]() |
8e85d0ddbd | ||
![]() |
bb4052d1d2 | ||
![]() |
1e759c64ed | ||
![]() |
b3c7f783ed | ||
![]() |
e0ae6aa2e0 | ||
![]() |
37fe5071c5 | ||
![]() |
ca716ec1dd | ||
![]() |
2137d8ef5d | ||
![]() |
8178fa6b62 | ||
![]() |
303a3cbbbe | ||
![]() |
9173b340c8 | ||
![]() |
818790650a | ||
![]() |
eb5041008d | ||
![]() |
152ab6dfd5 | ||
![]() |
192070c18e | ||
![]() |
f7f1a3e4b3 | ||
![]() |
9c8e9baa39 | ||
![]() |
217410e9a4 | ||
![]() |
e35d0cbcd6 | ||
![]() |
d9c40057e6 | ||
![]() |
86bcb8ca87 | ||
![]() |
cf4d94a7aa | ||
![]() |
df192bed4d | ||
![]() |
8d2371c14e | ||
![]() |
5ed669e0da | ||
![]() |
43dce69cce | ||
![]() |
4c53eb97d2 | ||
![]() |
b4b8593879 | ||
![]() |
46a91fde20 | ||
![]() |
8f826e6c2f | ||
![]() |
ebfea707c5 | ||
![]() |
8fc0175166 | ||
![]() |
dc43f56db1 | ||
![]() |
e5761f1e42 | ||
![]() |
8dd8252a46 | ||
![]() |
7ee4715af2 | ||
![]() |
1b3f931074 | ||
![]() |
30cde3e521 | ||
![]() |
6e9370458b | ||
![]() |
beae4502d4 | ||
![]() |
969395ccdb | ||
![]() |
3ee79c3937 | ||
![]() |
508c714932 | ||
![]() |
8f7582e1ec | ||
![]() |
bdbbe4adee | ||
![]() |
65478296cb | ||
![]() |
2342f7c5c6 | ||
![]() |
8266c1cff5 | ||
![]() |
c662c625a0 | ||
![]() |
c989648327 | ||
![]() |
a933ac1118 | ||
![]() |
b8afe7abcc | ||
![]() |
3ab02cc4ff | ||
![]() |
bedc3d0b50 | ||
![]() |
1398762e1d | ||
![]() |
e0a77da773 | ||
![]() |
c3f4cd3bf9 | ||
![]() |
f110255310 | ||
![]() |
155d2e65c2 | ||
![]() |
4d341e9876 |
@@ -6,4 +6,6 @@ README.md
|
|||||||
ROADMAP.md
|
ROADMAP.md
|
||||||
build
|
build
|
||||||
.env
|
.env
|
||||||
data.db
|
data.db
|
||||||
|
app/node_modules
|
||||||
|
app/build
|
||||||
|
@@ -2,8 +2,12 @@ ENV=production
|
|||||||
DATABASE_URL=data.db
|
DATABASE_URL=data.db
|
||||||
DATABASE_TYPE=sqlite
|
DATABASE_TYPE=sqlite
|
||||||
ADMIN_SECRET=admin
|
ADMIN_SECRET=admin
|
||||||
DISABLE_EMAIL_VERIFICATION=true
|
|
||||||
JWT_SECRET=random_string
|
JWT_SECRET=random_string
|
||||||
|
SENDER_EMAIL=info@authorizer.dev
|
||||||
|
SMTP_USERNAME=username
|
||||||
|
SMTP_PASSWORD=password
|
||||||
|
SMTP_HOST=smtp.mailtrap.io
|
||||||
|
SMTP_PORT=2525
|
||||||
JWT_TYPE=HS256
|
JWT_TYPE=HS256
|
||||||
ROLES=user
|
ROLES=user
|
||||||
DEFAULT_ROLES=user
|
DEFAULT_ROLES=user
|
||||||
|
178
.github/CONTRIBUTING.md
vendored
178
.github/CONTRIBUTING.md
vendored
@@ -49,3 +49,181 @@ Please ask as many questions as you need, either directly in the issue or on [Di
|
|||||||
5. Build the code `make clean && make`
|
5. Build the code `make clean && make`
|
||||||
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
|
> Note: if you don't have [`make`](https://www.ibm.com/docs/en/aix/7.2?topic=concepts-make-command), you can `cd` into `server` dir and build using the `go build` command
|
||||||
6. Run binary `./build/server`
|
6. Run binary `./build/server`
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
Make sure you test before creating PR.
|
||||||
|
|
||||||
|
If you want to test for all the databases that authorizer supports you will have to run `mongodb` & `arangodb` instances locally.
|
||||||
|
|
||||||
|
Setup mongodb & arangodb using Docker
|
||||||
|
|
||||||
|
```
|
||||||
|
docker run --name mongodb -d -p 27017:27017 mongo
|
||||||
|
|
||||||
|
// -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)
|
||||||
|
|
||||||
|
If you are adding new resolver,
|
||||||
|
|
||||||
|
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(t *testing.T, s TestSetup)`
|
||||||
|
2. Add your tests [here](https://github.com/authorizerdev/authorizer/blob/main/server/__test__/resolvers_test.go#L38)
|
||||||
|
|
||||||
|
**Command to run tests:**
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make test
|
||||||
|
```
|
||||||
|
|
||||||
|
**Manual Testing:**
|
||||||
|
|
||||||
|
For manually testing using graphql playground, you can paste following queries and mutations in your playground and test it
|
||||||
|
|
||||||
|
```gql
|
||||||
|
mutation Signup {
|
||||||
|
signup(
|
||||||
|
params: {
|
||||||
|
email: "lakhan@yopmail.com"
|
||||||
|
password: "test"
|
||||||
|
confirm_password: "test"
|
||||||
|
given_name: "lakhan"
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
message
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
family_name
|
||||||
|
given_name
|
||||||
|
email
|
||||||
|
email_verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation ResendEamil {
|
||||||
|
resend_verify_email(
|
||||||
|
params: { email: "lakhan@yopmail.com", identifier: "basic_auth_signup" }
|
||||||
|
) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query GetVerifyRequests {
|
||||||
|
_verification_requests {
|
||||||
|
id
|
||||||
|
token
|
||||||
|
expires
|
||||||
|
identifier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation VerifyEmail {
|
||||||
|
verify_email(params: { token: "" }) {
|
||||||
|
access_token
|
||||||
|
expires_at
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
given_name
|
||||||
|
email_verified
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation Login {
|
||||||
|
login(params: { email: "lakhan@yopmail.com", password: "test" }) {
|
||||||
|
access_token
|
||||||
|
expires_at
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
family_name
|
||||||
|
given_name
|
||||||
|
email
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query GetSession {
|
||||||
|
session {
|
||||||
|
access_token
|
||||||
|
expires_at
|
||||||
|
user {
|
||||||
|
id
|
||||||
|
given_name
|
||||||
|
family_name
|
||||||
|
email
|
||||||
|
email_verified
|
||||||
|
signup_methods
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation ForgotPassword {
|
||||||
|
forgot_password(params: { email: "lakhan@yopmail.com" }) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation ResetPassword {
|
||||||
|
reset_password(
|
||||||
|
params: { token: "", password: "test", confirm_password: "test" }
|
||||||
|
) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation UpdateProfile {
|
||||||
|
update_profile(params: { family_name: "samani" }) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
query GetUsers {
|
||||||
|
_users {
|
||||||
|
id
|
||||||
|
email
|
||||||
|
email_verified
|
||||||
|
given_name
|
||||||
|
family_name
|
||||||
|
picture
|
||||||
|
signup_methods
|
||||||
|
phone_number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation MagicLinkLogin {
|
||||||
|
magic_link_login(params: { email: "test@yopmail.com" }) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation Logout {
|
||||||
|
logout {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation UpdateUser {
|
||||||
|
_update_user(
|
||||||
|
params: {
|
||||||
|
id: "dafc9400-d603-4ade-997c-83fcd54bbd67"
|
||||||
|
roles: ["user", "admin"]
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
email
|
||||||
|
roles
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mutation DeleteUser {
|
||||||
|
_delete_user(params: { email: "signup.test134523@yopmail.com" }) {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
27
.github/workflows/release.yaml
vendored
27
.github/workflows/release.yaml
vendored
@@ -8,26 +8,23 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/setup-node@v2
|
||||||
|
with:
|
||||||
|
node-version: '16'
|
||||||
|
- uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: '^1.17.3'
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get install build-essential wget zip gcc-mingw-w64 && \
|
sudo apt-get install build-essential wget zip gcc-mingw-w64 && \
|
||||||
sudo apt-get remove --auto-remove golang-go && \
|
echo "/usr/bin/x86_64-w64-mingw32-gcc" >> GITHUB_PATH && \
|
||||||
sudo rm -rf /usr/bin/go &&\
|
|
||||||
wget --progress=dot:mega https://golang.org/dl/go1.17.1.linux-amd64.tar.gz -O go-linux.tar.gz && \
|
|
||||||
sudo tar -zxf go-linux.tar.gz && \
|
|
||||||
sudo mv go /usr/bin/ && \
|
|
||||||
sudo mkdir -p /go/bin /go/src /go/pkg && \
|
|
||||||
export GO_HOME=/usr/bin/go && \
|
|
||||||
export GOPATH=/go && \
|
|
||||||
export PATH=${GOPATH}/bin:${GO_HOME}/bin/:$PATH && \
|
|
||||||
echo "/usr/bin/go/bin" >> $GITHUB_PATH
|
|
||||||
echo "/usr/bin/x86_64-w64-mingw32-gcc" >> GITHUB_PATH
|
|
||||||
go version && \
|
|
||||||
wget --no-check-certificate --progress=dot:mega https://github.com/wangyoucao577/assets-uploader/releases/download/v0.3.0/github-assets-uploader-v0.3.0-linux-amd64.tar.gz -O github-assets-uploader.tar.gz && \
|
wget --no-check-certificate --progress=dot:mega https://github.com/wangyoucao577/assets-uploader/releases/download/v0.3.0/github-assets-uploader-v0.3.0-linux-amd64.tar.gz -O github-assets-uploader.tar.gz && \
|
||||||
tar -zxf github-assets-uploader.tar.gz && \
|
tar -zxf github-assets-uploader.tar.gz && \
|
||||||
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-dashboard
|
||||||
- name: Print Go paths
|
- name: Print Go paths
|
||||||
run: whereis go
|
run: whereis go
|
||||||
- name: Print Go Version
|
- name: Print Go Version
|
||||||
@@ -41,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} && \
|
||||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,6 +2,9 @@ server/server
|
|||||||
server/.env
|
server/.env
|
||||||
data
|
data
|
||||||
app/node_modules
|
app/node_modules
|
||||||
|
app/build
|
||||||
|
dashboard/node_modules
|
||||||
|
dashboard/build
|
||||||
build
|
build
|
||||||
.env
|
.env
|
||||||
data.db
|
data.db
|
||||||
|
24
Dockerfile
24
Dockerfile
@@ -1,5 +1,5 @@
|
|||||||
FROM golang:1.16-alpine as builder
|
FROM golang:1.17-alpine as go-builder
|
||||||
WORKDIR /app
|
WORKDIR /authorizer
|
||||||
COPY server server
|
COPY server server
|
||||||
COPY Makefile .
|
COPY Makefile .
|
||||||
|
|
||||||
@@ -7,15 +7,25 @@ ARG VERSION="latest"
|
|||||||
ENV VERSION="$VERSION"
|
ENV VERSION="$VERSION"
|
||||||
|
|
||||||
RUN echo "$VERSION"
|
RUN echo "$VERSION"
|
||||||
RUN apk add build-base nodejs &&\
|
RUN apk add build-base &&\
|
||||||
make clean && make && \
|
make clean && make && \
|
||||||
chmod 777 build/server
|
chmod 777 build/server
|
||||||
|
|
||||||
FROM alpine:latest
|
FROM node:17-alpine3.12 as node-builder
|
||||||
RUN apk --no-cache add ca-certificates
|
WORKDIR /authorizer
|
||||||
WORKDIR /root/
|
|
||||||
COPY app app
|
COPY app app
|
||||||
|
COPY dashboard dashboard
|
||||||
|
COPY Makefile .
|
||||||
|
RUN apk add build-base &&\
|
||||||
|
make build-app && \
|
||||||
|
make build-dashboard
|
||||||
|
|
||||||
|
FROM alpine:latest
|
||||||
|
WORKDIR /root/
|
||||||
|
RUN mkdir app dashboard
|
||||||
|
COPY --from=node-builder /authorizer/app/build app/build
|
||||||
|
COPY --from=node-builder /authorizer/dashboard/build dashboard/build
|
||||||
|
COPY --from=go-builder /authorizer/build build
|
||||||
COPY templates templates
|
COPY templates templates
|
||||||
COPY --from=builder /app/build build
|
|
||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
CMD [ "./build/server" ]
|
CMD [ "./build/server" ]
|
||||||
|
8
Makefile
8
Makefile
@@ -2,8 +2,12 @@ DEFAULT_VERSION=0.1.0-local
|
|||||||
VERSION := $(or $(VERSION),$(DEFAULT_VERSION))
|
VERSION := $(or $(VERSION),$(DEFAULT_VERSION))
|
||||||
|
|
||||||
cmd:
|
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:
|
||||||
|
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 test ./...
|
cd server && go clean --testcache && go test -v ./test
|
||||||
|
16
README.md
16
README.md
@@ -7,7 +7,7 @@
|
|||||||
Authorizer
|
Authorizer
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any SQL database.
|
**Authorizer** is an open-source authentication and authorization solution for your applications. Bring your database and have complete control over the user information. You can self-host authorizer instances and connect to any database (Currently supports [Postgres](https://www.postgresql.org/), [MySQL](https://www.mysql.com/), [SQLite](https://www.sqlite.org/index.html), [SQLServer](https://www.microsoft.com/en-us/sql-server/), [MongoDB](https://mongodb.com/),[ArangoDB](https://www.arangodb.com/)).
|
||||||
|
|
||||||
## Table of contents
|
## Table of contents
|
||||||
|
|
||||||
@@ -66,6 +66,7 @@ This guide helps you practice using Authorizer to evaluate it before you use it
|
|||||||
- [Install using source code](#install-using-source-code)
|
- [Install using source code](#install-using-source-code)
|
||||||
- [Install using binaries](#install-using-binaries)
|
- [Install using binaries](#install-using-binaries)
|
||||||
- [Install instance on heroku](#install-instance-on-Heroku)
|
- [Install instance on heroku](#install-instance-on-Heroku)
|
||||||
|
- [Install instance on railway.app](#install-instance-on-railway)
|
||||||
|
|
||||||
## Install using source code
|
## Install using source code
|
||||||
|
|
||||||
@@ -134,12 +135,23 @@ Deploy Authorizer using [heroku](https://github.com/authorizerdev/authorizer-her
|
|||||||
<br/><br/>
|
<br/><br/>
|
||||||
[](https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku)
|
[](https://heroku.com/deploy?template=https://github.com/authorizerdev/authorizer-heroku)
|
||||||
|
|
||||||
|
# Install instance on railway
|
||||||
|
|
||||||
|
Deploy production ready Authorizer instance using [railway.app](https://github.com/authorizerdev/authorizer-railway) with postgres and redis for free and build with it in 30seconds
|
||||||
|
<br/>
|
||||||
|
|
||||||
|
[](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fauthorizerdev%2Fauthorizer-railway&plugins=postgresql%2Credis&envs=ENV%2CDATABASE_TYPE%2CADMIN_SECRET%2CCOOKIE_NAME%2CJWT_ROLE_CLAIM%2CJWT_TYPE%2CJWT_SECRET%2CFACEBOOK_CLIENT_ID%2CFACEBOOK_CLIENT_SECRET%2CGOOGLE_CLIENT_ID%2CGOOGLE_CLIENT_SECRET%2CGITHUB_CLIENT_ID%2CGITHUB_CLIENT_SECRET%2CALLOWED_ORIGINS%2CROLES%2CPROTECTED_ROLES%2CDEFAULT_ROLES&optionalEnvs=FACEBOOK_CLIENT_ID%2CFACEBOOK_CLIENT_SECRET%2CGOOGLE_CLIENT_ID%2CGOOGLE_CLIENT_SECRET%2CGITHUB_CLIENT_ID%2CGITHUB_CLIENT_SECRET%2CALLOWED_ORIGINS%2CROLES%2CPROTECTED_ROLES%2CDEFAULT_ROLES&ENVDesc=Deployment+environment&DATABASE_TYPEDesc=With+railway+we+are+deploying+postgres+db&ADMIN_SECRETDesc=Secret+to+access+the+admin+apis&COOKIE_NAMEDesc=Name+of+http+only+cookie+that+will+be+used+as+session&FACEBOOK_CLIENT_IDDesc=Facebook+client+ID+for+facebook+login&FACEBOOK_CLIENT_SECRETDesc=Facebook+client+secret+for+facebook+login&GOOGLE_CLIENT_IDDesc=Google+client+ID+for+google+login&GOOGLE_CLIENT_SECRETDesc=Google+client+secret+for+google+login&GITHUB_CLIENT_IDDesc=Github+client+ID+for+github+login&GITHUB_CLIENT_SECRETDesc=Github+client+secret+for+github+login&ALLOWED_ORIGINSDesc=Whitelist+the+URL+for+which+this+instance+of+authorizer+is+allowed&ROLESDesc=Comma+separated+list+of+roles+that+platform+supports.+Default+role+is+user&PROTECTED_ROLESDesc=Comma+separated+list+of+protected+roles+for+which+sign-up+is+disabled&DEFAULT_ROLESDesc=Default+role+that+should+be+assigned+to+user.+It+should+be+one+from+the+list+of+%60ROLES%60+env.+Default+role+is+user&JWT_ROLE_CLAIMDesc=JWT+key+to+be+used+to+validate+the+role+field.&JWT_TYPEDesc=JWT+encryption+type&JWT_SECRETDesc=Random+string+that+will+be+used+for+encrypting+the+JWT+token&ENVDefault=PRODUCTION&DATABASE_TYPEDefault=postgres&COOKIE_NAMEDefault=authorizer&JWT_TYPEDefault=HS256&JWT_ROLE_CLAIMDefault=role)
|
||||||
|
|
||||||
### Things to consider
|
### Things to consider
|
||||||
|
|
||||||
- For social logins, you will need respective social platform key and secret
|
- For social logins, you will need respective social platform key and secret
|
||||||
- For having verified users, you will need an SMTP server with an email address and password using which system can send emails. The system will send a verification link to an email address. Once an email is verified then, only able to access it.
|
- For having verified users, you will need an SMTP server with an email address and password using which system can send emails. The system will send a verification link to an email address. Once an email is verified then, only able to access it.
|
||||||
> Note: One can always disable the email verification to allow open sign up, which is not recommended for production as anyone can use anyone's email address 😅
|
> Note: One can always disable the email verification to allow open sign up, which is not recommended for production as anyone can use anyone's email address 😅
|
||||||
- For persisting user sessions, you will need Redis URL. If you do not configure a Redis server, sessions will be persisted until the instance is up or not restarted. For better response time on authorization requests/middleware, we recommend deploying Redis on the same infra/network as your authorizer server.
|
- For persisting user sessions, you will need Redis URL (not in case of railway.app). If you do not configure a Redis server, sessions will be persisted until the instance is up or not restarted. For better response time on authorization requests/middleware, we recommend deploying Redis on the same infra/network as your authorizer server.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
- Check the testing instructions [here](https://github.com/authorizerdev/authorizer/blob/main/.github/CONTRIBUTING.md#testing)
|
||||||
|
|
||||||
## Integrating into your website
|
## Integrating into your website
|
||||||
|
|
||||||
|
10
TODO.md
10
TODO.md
@@ -1,5 +1,15 @@
|
|||||||
# Task List
|
# Task List
|
||||||
|
|
||||||
|
## Open ID compatible claims and schema
|
||||||
|
|
||||||
|
- [x] Rename `schema.graphqls` and re generate schema
|
||||||
|
- [x] Rename to snake case [files + schema]
|
||||||
|
- [x] Refactor db models
|
||||||
|
- [x] Check extra data in oauth profile and save accordingly
|
||||||
|
- [x] Update all the resolver to make them compatible with schema changes
|
||||||
|
- [x] Update JWT claims
|
||||||
|
- [x] Write integration tests for all resolvers
|
||||||
|
|
||||||
## Feature Multiple sessions
|
## Feature Multiple sessions
|
||||||
|
|
||||||
- Multiple sessions for users to login use hMset from redis for this
|
- Multiple sessions for users to login use hMset from redis for this
|
||||||
|
@@ -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`
|
||||||
|
@@ -1,16 +0,0 @@
|
|||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
||||||
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
||||||
sans-serif;
|
|
||||||
-webkit-font-smoothing: antialiased;
|
|
||||||
-moz-osx-font-smoothing: grayscale;
|
|
||||||
color: #374151;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
*,
|
|
||||||
*:before,
|
|
||||||
*:after {
|
|
||||||
box-sizing: inherit;
|
|
||||||
}
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
11
app/esbuild.config.js
Normal file
11
app/esbuild.config.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
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__,
|
||||||
|
});
|
185
app/package-lock.json
generated
185
app/package-lock.json
generated
@@ -5,111 +5,119 @@
|
|||||||
"requires": true,
|
"requires": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-js": {
|
"@authorizerdev/authorizer-js": {
|
||||||
"version": "0.1.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.0.tgz",
|
||||||
"integrity": "sha512-wNtTrFYg4qjvI+Sm58d9E8x+XaiwCA0DCyG87e37YDXmMlvvOmRQf05dxRRxZoxNC1WUkA3UYMTAclCKkonZAg==",
|
"integrity": "sha512-T2gurEYEP1T56j9IwDIWP1PsELoWcU7TBl5G0UsMqFQhKo7T6p2hM634Z7PS1yLegU3aihuvHGI0C0n4xSo0VQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"node-fetch": "^2.6.1"
|
"node-fetch": "^2.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@authorizerdev/authorizer-react": {
|
"@authorizerdev/authorizer-react": {
|
||||||
"version": "0.1.0",
|
"version": "0.4.0",
|
||||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.0.tgz",
|
||||||
"integrity": "sha512-+ro0CNKIvblEgRs5M0HevUYrXC6s433GinL7EubIL5XztN48GDBV2GI86lhl4WvjbYTZApZloXWczaZ51g8uig==",
|
"integrity": "sha512-ydE7xrP3cqeTtU923bK0+OIB1fnL0VHnbThcNa41n89XUPV3VBhZ23gxMg90nqon8JFE5g2TNz7+/qBCOQ7aZw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@authorizerdev/authorizer-js": "^0.1.0",
|
"@authorizerdev/authorizer-js": "^0.2.0",
|
||||||
"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"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/code-frame": {
|
"@babel/code-frame": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.16.7.tgz",
|
||||||
"integrity": "sha512-IF4EOMEV+bfYwOmNxGzSnjR2EmQod7f1UXOpZM3l4i4o4QNwzjtJAu/HxdjHq0aYBvdqMuQEY1eg0nqW9ZPORA==",
|
"integrity": "sha512-iAXqUn8IIeBTNd72xsFlgaXHkMBMt6y4HJp1tIaK465CWLT/fG1aqB7ykr95gHHmlBdGbFeWWfyB4NJJ0nmeIg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/highlight": "^7.16.0"
|
"@babel/highlight": "^7.16.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/generator": {
|
"@babel/generator": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz",
|
||||||
"integrity": "sha512-RR8hUCfRQn9j9RPKEVXo9LiwoxLPYn6hNZlvUOR8tSnaxlD0p0+la00ZP9/SnRt6HchKr+X0fO2r8vrETiJGew==",
|
"integrity": "sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.16.0",
|
"@babel/types": "^7.16.7",
|
||||||
"jsesc": "^2.5.1",
|
"jsesc": "^2.5.1",
|
||||||
"source-map": "^0.5.0"
|
"source-map": "^0.5.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-annotate-as-pure": {
|
"@babel/helper-annotate-as-pure": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz",
|
||||||
"integrity": "sha512-ItmYF9vR4zA8cByDocY05o0LGUkp1zhbTQOH1NFyl5xXEqlTJQCEJjieriw+aFpxo16swMxUnUiKS7a/r4vtHg==",
|
"integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.16.0"
|
"@babel/types": "^7.16.7"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"@babel/helper-environment-visitor": {
|
||||||
|
"version": "7.16.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz",
|
||||||
|
"integrity": "sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/types": "^7.16.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-function-name": {
|
"@babel/helper-function-name": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.16.7.tgz",
|
||||||
"integrity": "sha512-BZh4mEk1xi2h4HFjWUXRQX5AEx4rvaZxHgax9gcjdLWdkjsY7MKt5p0otjsg5noXw+pB+clMCjw+aEVYADMjog==",
|
"integrity": "sha512-QfDfEnIUyyBSR3HtrtGECuZ6DAyCkYFp7GHl75vFtTnn6pjKeK0T1DB5lLkFvBea8MdaiUABx3osbgLyInoejA==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-get-function-arity": "^7.16.0",
|
"@babel/helper-get-function-arity": "^7.16.7",
|
||||||
"@babel/template": "^7.16.0",
|
"@babel/template": "^7.16.7",
|
||||||
"@babel/types": "^7.16.0"
|
"@babel/types": "^7.16.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-get-function-arity": {
|
"@babel/helper-get-function-arity": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.16.7.tgz",
|
||||||
"integrity": "sha512-ASCquNcywC1NkYh/z7Cgp3w31YW8aojjYIlNg4VeJiHkqyP4AzIvr4qx7pYDb4/s8YcsZWqqOSxgkvjUz1kpDQ==",
|
"integrity": "sha512-flc+RLSOBXzNzVhcLu6ujeHUrD6tANAOU5ojrRx/as+tbzf8+stUCj7+IfRRoAbEZqj/ahXEMsjhOhgeZsrnTw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.16.0"
|
"@babel/types": "^7.16.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-hoist-variables": {
|
"@babel/helper-hoist-variables": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.16.7.tgz",
|
||||||
"integrity": "sha512-1AZlpazjUR0EQZQv3sgRNfM9mEVWPK3M6vlalczA+EECcPz3XPh6VplbErL5UoMpChhSck5wAJHthlj1bYpcmg==",
|
"integrity": "sha512-m04d/0Op34H5v7pbZw6pSKP7weA6lsMvfiIAMeIvkY/R4xQtBSMFEigu9QTZ2qB/9l22vsxtM8a+Q8CzD255fg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.16.0"
|
"@babel/types": "^7.16.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-module-imports": {
|
"@babel/helper-module-imports": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz",
|
||||||
"integrity": "sha512-kkH7sWzKPq0xt3H1n+ghb4xEMP8k0U7XV3kkB+ZGy69kDk2ySFW1qPi06sjKzFY3t1j6XbJSqr4mF9L7CYVyhg==",
|
"integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.16.0"
|
"@babel/types": "^7.16.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-split-export-declaration": {
|
"@babel/helper-split-export-declaration": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.16.7.tgz",
|
||||||
"integrity": "sha512-0YMMRpuDFNGTHNRiiqJX19GjNXA4H0E8jZ2ibccfSxaCogbm3am5WN/2nQNj0YnQwGWM1J06GOcQ2qnh3+0paw==",
|
"integrity": "sha512-xbWoy/PFoxSWazIToT9Sif+jJTlrMcndIsaOKvTA6u7QEo7ilkRZpjew18/W3c7nm8fXdUDXh02VXTbZ0pGDNw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/types": "^7.16.0"
|
"@babel/types": "^7.16.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/helper-validator-identifier": {
|
"@babel/helper-validator-identifier": {
|
||||||
"version": "7.15.7",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz",
|
||||||
"integrity": "sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w=="
|
"integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw=="
|
||||||
},
|
},
|
||||||
"@babel/highlight": {
|
"@babel/highlight": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz",
|
||||||
"integrity": "sha512-t8MH41kUQylBtu2+4IQA3atqevA2lRgqA2wyVB/YiWmsDSuylZZuXOUy9ric30hfzauEFfdsuk/eXTRrGrfd0g==",
|
"integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.15.7",
|
"@babel/helper-validator-identifier": "^7.16.7",
|
||||||
"chalk": "^2.0.0",
|
"chalk": "^2.0.0",
|
||||||
"js-tokens": "^4.0.0"
|
"js-tokens": "^4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/parser": {
|
"@babel/parser": {
|
||||||
"version": "7.16.3",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz",
|
||||||
"integrity": "sha512-dcNwU1O4sx57ClvLBVFbEgx0UZWfd0JQX5X6fxFRCLHelFBGXFfSz6Y0FAq2PEwUqlqLkdVjVr4VASEOuUnLJw=="
|
"integrity": "sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA=="
|
||||||
},
|
},
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.14.8",
|
"version": "7.14.8",
|
||||||
@@ -120,37 +128,38 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/template": {
|
"@babel/template": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.16.7.tgz",
|
||||||
"integrity": "sha512-MnZdpFD/ZdYhXwiunMqqgyZyucaYsbL0IrjoGjaVhGilz+x8YB++kRfygSOIj1yOtWKPlx7NBp+9I1RQSgsd5A==",
|
"integrity": "sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.16.0",
|
"@babel/code-frame": "^7.16.7",
|
||||||
"@babel/parser": "^7.16.0",
|
"@babel/parser": "^7.16.7",
|
||||||
"@babel/types": "^7.16.0"
|
"@babel/types": "^7.16.7"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/traverse": {
|
"@babel/traverse": {
|
||||||
"version": "7.16.3",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz",
|
||||||
"integrity": "sha512-eolumr1vVMjqevCpwVO99yN/LoGL0EyHiLO5I043aYQvwOJ9eR5UsZSClHVCzfhBduMAsSzgA/6AyqPjNayJag==",
|
"integrity": "sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/code-frame": "^7.16.0",
|
"@babel/code-frame": "^7.16.7",
|
||||||
"@babel/generator": "^7.16.0",
|
"@babel/generator": "^7.16.7",
|
||||||
"@babel/helper-function-name": "^7.16.0",
|
"@babel/helper-environment-visitor": "^7.16.7",
|
||||||
"@babel/helper-hoist-variables": "^7.16.0",
|
"@babel/helper-function-name": "^7.16.7",
|
||||||
"@babel/helper-split-export-declaration": "^7.16.0",
|
"@babel/helper-hoist-variables": "^7.16.7",
|
||||||
"@babel/parser": "^7.16.3",
|
"@babel/helper-split-export-declaration": "^7.16.7",
|
||||||
"@babel/types": "^7.16.0",
|
"@babel/parser": "^7.16.7",
|
||||||
|
"@babel/types": "^7.16.7",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"globals": "^11.1.0"
|
"globals": "^11.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@babel/types": {
|
"@babel/types": {
|
||||||
"version": "7.16.0",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.0.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz",
|
||||||
"integrity": "sha512-PJgg/k3SdLsGb3hhisFvtLOw5ts113klrpLuIPtCJIU+BB24fqq6lf8RWqKJEjzqXR9AEH1rIb5XTqwBHB+kQg==",
|
"integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-validator-identifier": "^7.15.7",
|
"@babel/helper-validator-identifier": "^7.16.7",
|
||||||
"to-fast-properties": "^2.0.0"
|
"to-fast-properties": "^2.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -241,12 +250,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"babel-plugin-styled-components": {
|
"babel-plugin-styled-components": {
|
||||||
"version": "1.13.3",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-1.13.3.tgz",
|
"resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.0.2.tgz",
|
||||||
"integrity": "sha512-meGStRGv+VuKA/q0/jXxrPNWEm4LPfYIqxooDTdmh8kFsP/Ph7jJG5rUPwUPX3QHUvggwdbgdGpo88P/rRYsVw==",
|
"integrity": "sha512-7eG5NE8rChnNTDxa6LQfynwgHTVOYYaHJbUYSlOhk8QBXIQiMBKq4gyfHBBKPrxUcVBXVJL61ihduCpCQbuNbw==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/helper-annotate-as-pure": "^7.15.4",
|
"@babel/helper-annotate-as-pure": "^7.16.0",
|
||||||
"@babel/helper-module-imports": "^7.15.4",
|
"@babel/helper-module-imports": "^7.16.0",
|
||||||
"babel-plugin-syntax-jsx": "^6.18.0",
|
"babel-plugin-syntax-jsx": "^6.18.0",
|
||||||
"lodash": "^4.17.11"
|
"lodash": "^4.17.11"
|
||||||
}
|
}
|
||||||
@@ -305,9 +314,9 @@
|
|||||||
"integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw=="
|
"integrity": "sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw=="
|
||||||
},
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.2",
|
"version": "4.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||||
"integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==",
|
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"ms": "2.1.2"
|
"ms": "2.1.2"
|
||||||
}
|
}
|
||||||
@@ -323,9 +332,9 @@
|
|||||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
|
||||||
},
|
},
|
||||||
"final-form": {
|
"final-form": {
|
||||||
"version": "4.20.4",
|
"version": "4.20.6",
|
||||||
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.4.tgz",
|
"resolved": "https://registry.npmjs.org/final-form/-/final-form-4.20.6.tgz",
|
||||||
"integrity": "sha512-hyoOVVilPLpkTvgi+FSJkFZrh0Yhy4BhE6lk/NiBwrF4aRV8/ykKEyXYvQH/pfUbRkOosvpESYouFb+FscsLrw==",
|
"integrity": "sha512-fCdwIj49KOaFfDRlXB57Eo+GghIMZQWrA9TakQI3C9uQxHwaFHXqZSNRlUdfnQmNNeySwGOaGPZCvjy58hyv4w==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"@babel/runtime": "^7.10.0"
|
"@babel/runtime": "^7.10.0"
|
||||||
}
|
}
|
||||||
@@ -432,9 +441,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"postcss-value-parser": {
|
"postcss-value-parser": {
|
||||||
"version": "4.1.0",
|
"version": "4.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
|
||||||
"integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ=="
|
"integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ=="
|
||||||
},
|
},
|
||||||
"prop-types": {
|
"prop-types": {
|
||||||
"version": "15.7.2",
|
"version": "15.7.2",
|
||||||
@@ -481,9 +490,9 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": {
|
"@babel/runtime": {
|
||||||
"version": "7.16.3",
|
"version": "7.16.7",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.7.tgz",
|
||||||
"integrity": "sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==",
|
"integrity": "sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ==",
|
||||||
"requires": {
|
"requires": {
|
||||||
"regenerator-runtime": "^0.13.4"
|
"regenerator-runtime": "^0.13.4"
|
||||||
}
|
}
|
||||||
|
@@ -4,13 +4,14 @@
|
|||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "esbuild src/index.tsx --bundle --minify --sourcemap --outfile=build/bundle.js"
|
"build": "rm -rf build && NODE_ENV=production node ./esbuild.config.js",
|
||||||
|
"start": "NODE_ENV=development node ./esbuild.config.js"
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "Lakhan Samani",
|
"author": "Lakhan Samani",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@authorizerdev/authorizer-react": "^0.1.0",
|
"@authorizerdev/authorizer-react": "latest",
|
||||||
"@types/react": "^17.0.15",
|
"@types/react": "^17.0.15",
|
||||||
"@types/react-dom": "^17.0.9",
|
"@types/react-dom": "^17.0.9",
|
||||||
"esbuild": "^0.12.17",
|
"esbuild": "^0.12.17",
|
||||||
|
@@ -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,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
@@ -1,9 +1,10 @@
|
|||||||
import React, { useEffect } from 'react';
|
import React, { useEffect, lazy, Suspense } from 'react';
|
||||||
import { Switch, Route } from 'react-router-dom';
|
import { Switch, Route } from 'react-router-dom';
|
||||||
import { useAuthorizer } from '@authorizerdev/authorizer-react';
|
import { useAuthorizer } from '@authorizerdev/authorizer-react';
|
||||||
import Dashboard from './pages/dashboard';
|
|
||||||
import Login from './pages/login';
|
const ResetPassword = lazy(() => import('./pages/rest-password'));
|
||||||
import ResetPassword from './pages/rest-password';
|
const Login = lazy(() => import('./pages/login'));
|
||||||
|
const Dashboard = lazy(() => import('./pages/dashboard'));
|
||||||
|
|
||||||
export default function Root() {
|
export default function Root() {
|
||||||
const { token, loading, config } = useAuthorizer();
|
const { token, loading, config } = useAuthorizer();
|
||||||
@@ -24,22 +25,26 @@ export default function Root() {
|
|||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Suspense fallback={<></>}>
|
||||||
<Route path="/app" exact>
|
<Switch>
|
||||||
<Dashboard />
|
<Route path="/app" exact>
|
||||||
</Route>
|
<Dashboard />
|
||||||
</Switch>
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Switch>
|
<Suspense fallback={<></>}>
|
||||||
<Route path="/app" exact>
|
<Switch>
|
||||||
<Login />
|
<Route path="/app" exact>
|
||||||
</Route>
|
<Login />
|
||||||
<Route path="/app/reset-password">
|
</Route>
|
||||||
<ResetPassword />
|
<Route path="/app/reset-password">
|
||||||
</Route>
|
<ResetPassword />
|
||||||
</Switch>
|
</Route>
|
||||||
|
</Switch>
|
||||||
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
16
app/src/index.css
Normal file
16
app/src/index.css
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
color: #374151;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
*,
|
||||||
|
*:before,
|
||||||
|
*:after {
|
||||||
|
box-sizing: inherit;
|
||||||
|
}
|
@@ -1,5 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import App from './App';
|
import App from './App';
|
||||||
|
import './index.css';
|
||||||
|
|
||||||
ReactDOM.render(<App />, document.getElementById('root'));
|
ReactDOM.render(<App />, document.getElementById('root'));
|
||||||
|
@@ -2,11 +2,11 @@ import React, { Fragment } from 'react';
|
|||||||
import { AuthorizerResetPassword } from '@authorizerdev/authorizer-react';
|
import { AuthorizerResetPassword } from '@authorizerdev/authorizer-react';
|
||||||
|
|
||||||
export default function ResetPassword() {
|
export default function ResetPassword() {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<h1 style={{ textAlign: 'center' }}>Reset Password</h1>
|
<h1 style={{ textAlign: 'center' }}>Reset Password</h1>
|
||||||
<br />
|
<br />
|
||||||
<AuthorizerResetPassword />
|
<AuthorizerResetPassword />
|
||||||
</Fragment>
|
</Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
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 && 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,46 +0,0 @@
|
|||||||
package constants
|
|
||||||
|
|
||||||
var (
|
|
||||||
ADMIN_SECRET = ""
|
|
||||||
ENV = ""
|
|
||||||
VERSION = ""
|
|
||||||
DATABASE_TYPE = ""
|
|
||||||
DATABASE_URL = ""
|
|
||||||
SMTP_HOST = ""
|
|
||||||
SMTP_PORT = ""
|
|
||||||
SENDER_EMAIL = ""
|
|
||||||
SENDER_PASSWORD = ""
|
|
||||||
JWT_TYPE = ""
|
|
||||||
JWT_SECRET = ""
|
|
||||||
ALLOWED_ORIGINS = []string{}
|
|
||||||
AUTHORIZER_URL = ""
|
|
||||||
APP_URL = ""
|
|
||||||
PORT = "8080"
|
|
||||||
REDIS_URL = ""
|
|
||||||
IS_PROD = false
|
|
||||||
COOKIE_NAME = ""
|
|
||||||
RESET_PASSWORD_URL = ""
|
|
||||||
DISABLE_EMAIL_VERIFICATION = "false"
|
|
||||||
DISABLE_BASIC_AUTHENTICATION = "false"
|
|
||||||
DISABLE_MAGIC_LOGIN = "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"
|
|
||||||
)
|
|
16
server/constants/db_types.go
Normal file
16
server/constants/db_types.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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"
|
||||||
|
)
|
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,7 +1,8 @@
|
|||||||
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
|
||||||
GoogleUserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo"
|
GoogleUserInfoURL = "https://www.googleapis.com/oauth2/v3/userinfo"
|
||||||
// Ref: https://github.com/qor/auth/blob/master/providers/facebook/facebook.go#L18
|
// Ref: https://github.com/qor/auth/blob/master/providers/facebook/facebook.go#L18
|
||||||
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
|
FacebookUserInfoURL = "https://graph.facebook.com/me?fields=id,first_name,last_name,name,email,picture&access_token="
|
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"
|
||||||
|
)
|
@@ -4,61 +4,41 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
|
|
||||||
"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"
|
||||||
"github.com/google/uuid"
|
"github.com/authorizerdev/authorizer/server/db/providers/arangodb"
|
||||||
"gorm.io/driver/mysql"
|
"github.com/authorizerdev/authorizer/server/db/providers/mongodb"
|
||||||
"gorm.io/driver/postgres"
|
"github.com/authorizerdev/authorizer/server/db/providers/sql"
|
||||||
"gorm.io/driver/sqlite"
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/schema"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Manager interface {
|
// Provider returns the current database provider
|
||||||
SaveUser(user User) (User, error)
|
var Provider providers.Provider
|
||||||
UpdateUser(user User) (User, error)
|
|
||||||
GetUsers() ([]User, error)
|
|
||||||
GetUserByEmail(email string) (User, error)
|
|
||||||
GetUserByID(email string) (User, error)
|
|
||||||
UpdateVerificationTime(verifiedAt int64, id uuid.UUID) error
|
|
||||||
AddVerification(verification VerificationRequest) (VerificationRequest, error)
|
|
||||||
GetVerificationByToken(token string) (VerificationRequest, error)
|
|
||||||
DeleteToken(email string) error
|
|
||||||
GetVerificationRequests() ([]VerificationRequest, error)
|
|
||||||
GetVerificationByEmail(email string) (VerificationRequest, error)
|
|
||||||
DeleteUser(email string) error
|
|
||||||
SaveRoles(roles []Role) error
|
|
||||||
SaveSession(session Session) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type manager struct {
|
|
||||||
db *gorm.DB
|
|
||||||
}
|
|
||||||
|
|
||||||
var Mgr Manager
|
|
||||||
|
|
||||||
func InitDB() {
|
func InitDB() {
|
||||||
var db *gorm.DB
|
|
||||||
var err error
|
var err error
|
||||||
ormConfig := &gorm.Config{
|
|
||||||
NamingStrategy: schema.NamingStrategy{
|
isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb
|
||||||
TablePrefix: "authorizer_",
|
isArangoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
|
||||||
},
|
isMongoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
|
||||||
}
|
|
||||||
if constants.DATABASE_TYPE == enum.Postgres.String() {
|
if isSQL {
|
||||||
db, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig)
|
Provider, err = sql.NewProvider()
|
||||||
}
|
if err != nil {
|
||||||
if constants.DATABASE_TYPE == enum.Mysql.String() {
|
log.Fatal("=> error setting sql provider:", err)
|
||||||
db, err = gorm.Open(mysql.Open(constants.DATABASE_URL), ormConfig)
|
}
|
||||||
}
|
|
||||||
if constants.DATABASE_TYPE == enum.Sqlite.String() {
|
|
||||||
db, err = gorm.Open(sqlite.Open(constants.DATABASE_URL), ormConfig)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if isArangoDB {
|
||||||
log.Fatal("Failed to init db:", err)
|
Provider, err = arangodb.NewProvider()
|
||||||
} else {
|
if err != nil {
|
||||||
db.AutoMigrate(&User{}, &VerificationRequest{}, &Role{}, &Session{})
|
log.Fatal("=> error setting arangodb provider:", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Mgr = &manager{db: db}
|
if isMongoDB {
|
||||||
|
Provider, err = mongodb.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"`
|
||||||
|
}
|
24
server/db/models/user.go
Normal file
24
server/db/models/user.go
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package models
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
}
|
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
|
||||||
|
}
|
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
|
||||||
|
}
|
88
server/db/providers/mongodb/mongodb.go
Normal file
88
server/db/providers/mongodb/mongodb.go
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
package mongodb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/mongo"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/options"
|
||||||
|
"go.mongodb.org/mongo-driver/mongo/readpref"
|
||||||
|
)
|
||||||
|
|
||||||
|
type provider struct {
|
||||||
|
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)
|
||||||
|
mongodbOptions.ConnectTimeout = &maxWait
|
||||||
|
mongoClient, err := mongo.NewClient(mongodbOptions)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx, _ := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
|
err = mongoClient.Connect(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = mongoClient.Ping(ctx, readpref.Primary())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mongodb := mongoClient.Database(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseName), options.Database())
|
||||||
|
|
||||||
|
mongodb.CreateCollection(ctx, models.Collections.User, options.CreateCollection())
|
||||||
|
userCollection := mongodb.Collection(models.Collections.User, options.Collection())
|
||||||
|
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
mongo.IndexModel{
|
||||||
|
Keys: bson.M{"email": 1},
|
||||||
|
Options: options.Index().SetUnique(true).SetSparse(true),
|
||||||
|
},
|
||||||
|
}, options.CreateIndexes())
|
||||||
|
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
mongo.IndexModel{
|
||||||
|
Keys: bson.M{"phone_number": 1},
|
||||||
|
Options: options.Index().SetUnique(true).SetSparse(true).SetPartialFilterExpression(map[string]interface{}{
|
||||||
|
"phone_number": map[string]string{"$type": "string"},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
|
mongodb.CreateCollection(ctx, models.Collections.VerificationRequest, options.CreateCollection())
|
||||||
|
verificationRequestCollection := mongodb.Collection(models.Collections.VerificationRequest, options.Collection())
|
||||||
|
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
mongo.IndexModel{
|
||||||
|
Keys: bson.M{"email": 1, "identifier": 1},
|
||||||
|
Options: options.Index().SetUnique(true).SetSparse(true),
|
||||||
|
},
|
||||||
|
}, options.CreateIndexes())
|
||||||
|
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
mongo.IndexModel{
|
||||||
|
Keys: bson.M{"token": 1},
|
||||||
|
Options: options.Index().SetSparse(true),
|
||||||
|
},
|
||||||
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
|
mongodb.CreateCollection(ctx, models.Collections.Session, options.CreateCollection())
|
||||||
|
sessionCollection := mongodb.Collection(models.Collections.Session, options.Collection())
|
||||||
|
sessionCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
|
||||||
|
mongo.IndexModel{
|
||||||
|
Keys: bson.M{"user_id": 1},
|
||||||
|
Options: options.Index().SetSparse(true),
|
||||||
|
},
|
||||||
|
}, options.CreateIndexes())
|
||||||
|
|
||||||
|
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,34 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Role struct {
|
|
||||||
ID uuid.UUID `gorm:"type:uuid;"`
|
|
||||||
Role string `gorm:"unique"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Role) BeforeCreate(tx *gorm.DB) (err error) {
|
|
||||||
r.ID = uuid.New()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveRoles function to save roles
|
|
||||||
func (mgr *manager) SaveRoles(roles []Role) error {
|
|
||||||
res := mgr.db.Clauses(
|
|
||||||
clause.OnConflict{
|
|
||||||
DoNothing: true,
|
|
||||||
}).Create(&roles)
|
|
||||||
if res.Error != nil {
|
|
||||||
log.Println(`Error saving roles`)
|
|
||||||
return res.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,39 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Session struct {
|
|
||||||
ID uuid.UUID `gorm:"type:uuid;"`
|
|
||||||
UserID uuid.UUID `gorm:"type:uuid;"`
|
|
||||||
User User
|
|
||||||
UserAgent string
|
|
||||||
IP string
|
|
||||||
CreatedAt int64 `gorm:"autoCreateTime"`
|
|
||||||
UpdatedAt int64 `gorm:"autoUpdateTime"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Session) BeforeCreate(tx *gorm.DB) (err error) {
|
|
||||||
r.ID = uuid.New()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveSession function to save user sessiosn
|
|
||||||
func (mgr *manager) SaveSession(session Session) error {
|
|
||||||
res := mgr.db.Clauses(
|
|
||||||
clause.OnConflict{
|
|
||||||
DoNothing: true,
|
|
||||||
}).Create(&session)
|
|
||||||
if res.Error != nil {
|
|
||||||
log.Println(`Error saving session`, res.Error)
|
|
||||||
return res.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,119 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
type User struct {
|
|
||||||
ID uuid.UUID `gorm:"type:uuid;"`
|
|
||||||
FirstName string
|
|
||||||
LastName string
|
|
||||||
Email string `gorm:"unique"`
|
|
||||||
Password string
|
|
||||||
SignupMethod string
|
|
||||||
EmailVerifiedAt int64
|
|
||||||
CreatedAt int64 `gorm:"autoCreateTime"`
|
|
||||||
UpdatedAt int64 `gorm:"autoUpdateTime"`
|
|
||||||
Image string
|
|
||||||
Roles string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (u *User) BeforeCreate(tx *gorm.DB) (err error) {
|
|
||||||
u.ID = uuid.New()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// SaveUser function to add user even with email conflict
|
|
||||||
func (mgr *manager) SaveUser(user User) (User, error) {
|
|
||||||
result := mgr.db.Clauses(
|
|
||||||
clause.OnConflict{
|
|
||||||
UpdateAll: true,
|
|
||||||
Columns: []clause.Column{{Name: "email"}},
|
|
||||||
}).Create(&user)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
log.Println(result.Error)
|
|
||||||
return user, result.Error
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UpdateUser function to update user with ID conflict
|
|
||||||
func (mgr *manager) UpdateUser(user User) (User, error) {
|
|
||||||
user.UpdatedAt = time.Now().Unix()
|
|
||||||
result := mgr.db.Clauses(
|
|
||||||
clause.OnConflict{
|
|
||||||
UpdateAll: true,
|
|
||||||
Columns: []clause.Column{{Name: "email"}},
|
|
||||||
}).Create(&user)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
log.Println(result.Error)
|
|
||||||
return user, result.Error
|
|
||||||
}
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsers function to get all users
|
|
||||||
func (mgr *manager) GetUsers() ([]User, error) {
|
|
||||||
var users []User
|
|
||||||
result := mgr.db.Find(&users)
|
|
||||||
if result.Error != nil {
|
|
||||||
log.Println(result.Error)
|
|
||||||
return users, result.Error
|
|
||||||
}
|
|
||||||
return users, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mgr *manager) GetUserByEmail(email string) (User, error) {
|
|
||||||
var user User
|
|
||||||
result := mgr.db.Where("email = ?", email).First(&user)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
return user, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mgr *manager) GetUserByID(id string) (User, error) {
|
|
||||||
var user User
|
|
||||||
result := mgr.db.Where("id = ?", id).First(&user)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
return user, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return user, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mgr *manager) UpdateVerificationTime(verifiedAt int64, id uuid.UUID) error {
|
|
||||||
user := &User{
|
|
||||||
ID: id,
|
|
||||||
}
|
|
||||||
result := mgr.db.Model(&user).Where("id = ?", id).Update("email_verified_at", verifiedAt)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mgr *manager) DeleteUser(email string) error {
|
|
||||||
var user User
|
|
||||||
result := mgr.db.Where("email = ?", email).Delete(&user)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
log.Println(`Error deleting user:`, result.Error)
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@@ -1,86 +0,0 @@
|
|||||||
package db
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
|
|
||||||
"github.com/google/uuid"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
"gorm.io/gorm/clause"
|
|
||||||
)
|
|
||||||
|
|
||||||
type VerificationRequest struct {
|
|
||||||
ID uuid.UUID `gorm:"type:uuid;"`
|
|
||||||
Token string `gorm:"index"`
|
|
||||||
Identifier string
|
|
||||||
ExpiresAt int64
|
|
||||||
CreatedAt int64 `gorm:"autoCreateTime"`
|
|
||||||
UpdatedAt int64 `gorm:"autoUpdateTime"`
|
|
||||||
Email string `gorm:"unique"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (v *VerificationRequest) BeforeCreate(tx *gorm.DB) (err error) {
|
|
||||||
v.ID = uuid.New()
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddVerification function to add verification record
|
|
||||||
func (mgr *manager) AddVerification(verification VerificationRequest) (VerificationRequest, error) {
|
|
||||||
result := mgr.db.Clauses(clause.OnConflict{
|
|
||||||
Columns: []clause.Column{{Name: "email"}},
|
|
||||||
DoUpdates: clause.AssignmentColumns([]string{"token", "identifier", "expires_at"}),
|
|
||||||
}).Create(&verification)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
log.Println(`Error saving verification record`, result.Error)
|
|
||||||
return verification, result.Error
|
|
||||||
}
|
|
||||||
return verification, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mgr *manager) GetVerificationByToken(token string) (VerificationRequest, error) {
|
|
||||||
var verification VerificationRequest
|
|
||||||
result := mgr.db.Where("token = ?", token).First(&verification)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
log.Println(`Error getting verification token:`, result.Error)
|
|
||||||
return verification, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return verification, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, error) {
|
|
||||||
var verification VerificationRequest
|
|
||||||
result := mgr.db.Where("email = ?", email).First(&verification)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
log.Println(`Error getting verification token:`, result.Error)
|
|
||||||
return verification, result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return verification, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mgr *manager) DeleteToken(email string) error {
|
|
||||||
var verification VerificationRequest
|
|
||||||
result := mgr.db.Where("email = ?", email).Delete(&verification)
|
|
||||||
|
|
||||||
if result.Error != nil {
|
|
||||||
log.Println(`Error deleting token:`, result.Error)
|
|
||||||
return result.Error
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetUsers function to get all users
|
|
||||||
func (mgr *manager) GetVerificationRequests() ([]VerificationRequest, error) {
|
|
||||||
var verificationRequests []VerificationRequest
|
|
||||||
result := mgr.db.Find(&verificationRequests)
|
|
||||||
if result.Error != nil {
|
|
||||||
log.Println(result.Error)
|
|
||||||
return verificationRequests, result.Error
|
|
||||||
}
|
|
||||||
return verificationRequests, nil
|
|
||||||
}
|
|
@@ -2,89 +2,48 @@ package email
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"crypto/tls"
|
||||||
|
"encoding/json"
|
||||||
"log"
|
"log"
|
||||||
"mime/quotedprintable"
|
"strconv"
|
||||||
"net/smtp"
|
"text/template"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
gomail "gopkg.in/mail.v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
// addEmailTemplate is used to add html template in email body
|
||||||
Using: https://github.com/tangingw/go_smtp/blob/master/send_mail.go
|
func addEmailTemplate(a string, b map[string]interface{}, templateName string) string {
|
||||||
For gmail add instruction to enable less security
|
tmpl, err := template.New(templateName).Parse(a)
|
||||||
// https://myaccount.google.com/u/0/lesssecureapps
|
|
||||||
// https://www.google.com/settings/security/lesssecureapps
|
|
||||||
// https://stackoverflow.com/questions/19877246/nodemailer-with-gmail-and-nodejs
|
|
||||||
**/
|
|
||||||
|
|
||||||
// TODO -> try using gomail.v2
|
|
||||||
|
|
||||||
type Sender struct {
|
|
||||||
User string
|
|
||||||
Password string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSender() Sender {
|
|
||||||
return Sender{User: constants.SENDER_EMAIL, Password: constants.SENDER_PASSWORD}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sender Sender) SendMail(Dest []string, Subject, bodyMessage string) error {
|
|
||||||
msg := "From: " + sender.User + "\n" +
|
|
||||||
"To: " + strings.Join(Dest, ",") + "\n" +
|
|
||||||
"Subject: " + Subject + "\n" + bodyMessage
|
|
||||||
|
|
||||||
err := smtp.SendMail(constants.SMTP_HOST+":"+constants.SMTP_PORT,
|
|
||||||
smtp.PlainAuth("", sender.User, sender.Password, constants.SMTP_HOST),
|
|
||||||
sender.User, Dest, []byte(msg))
|
|
||||||
if err != nil {
|
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 {
|
||||||
|
m := gomail.NewMessage()
|
||||||
|
m.SetHeader("From", envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySenderEmail))
|
||||||
|
m.SetHeader("To", to...)
|
||||||
|
m.SetHeader("Subject", Subject)
|
||||||
|
m.SetBody("text/html", bodyMessage)
|
||||||
|
port, _ := strconv.Atoi(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPort))
|
||||||
|
d := gomail.NewDialer(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpHost), port, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpUsername), envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeySmtpPassword))
|
||||||
|
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyEnv) == "development" {
|
||||||
|
d.TLSConfig = &tls.Config{InsecureSkipVerify: true}
|
||||||
|
}
|
||||||
|
if err := d.DialAndSend(m); err != nil {
|
||||||
log.Printf("smtp error: %s", err)
|
log.Printf("smtp error: %s", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sender Sender) WriteEmail(dest []string, contentType, subject, bodyMessage string) string {
|
|
||||||
header := make(map[string]string)
|
|
||||||
header["From"] = sender.User
|
|
||||||
|
|
||||||
receipient := ""
|
|
||||||
|
|
||||||
for _, user := range dest {
|
|
||||||
receipient = receipient + user
|
|
||||||
}
|
|
||||||
|
|
||||||
header["To"] = receipient
|
|
||||||
header["Subject"] = subject
|
|
||||||
header["MIME-Version"] = "1.0"
|
|
||||||
header["Content-Type"] = fmt.Sprintf("%s; charset=\"utf-8\"", contentType)
|
|
||||||
header["Content-Transfer-Encoding"] = "quoted-printable"
|
|
||||||
header["Content-Disposition"] = "inline"
|
|
||||||
|
|
||||||
message := ""
|
|
||||||
|
|
||||||
for key, value := range header {
|
|
||||||
message += fmt.Sprintf("%s: %s\r\n", key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
var encodedMessage bytes.Buffer
|
|
||||||
|
|
||||||
finalMessage := quotedprintable.NewWriter(&encodedMessage)
|
|
||||||
finalMessage.Write([]byte(bodyMessage))
|
|
||||||
finalMessage.Close()
|
|
||||||
|
|
||||||
message += "\r\n" + encodedMessage.String()
|
|
||||||
|
|
||||||
return message
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sender *Sender) WriteHTMLEmail(dest []string, subject, bodyMessage string) string {
|
|
||||||
return sender.WriteEmail(dest, "text/html", subject, bodyMessage)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sender *Sender) WritePlainEmail(dest []string, subject, bodyMessage string) string {
|
|
||||||
return sender.WriteEmail(dest, "text/plain", subject, bodyMessage)
|
|
||||||
}
|
|
||||||
|
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,17 +0,0 @@
|
|||||||
package enum
|
|
||||||
|
|
||||||
type DbType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
Postgres DbType = iota
|
|
||||||
Sqlite
|
|
||||||
Mysql
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d DbType) String() string {
|
|
||||||
return [...]string{
|
|
||||||
"postgres",
|
|
||||||
"sqlite",
|
|
||||||
"mysql",
|
|
||||||
}[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
|
|
||||||
MagicLink
|
|
||||||
Google
|
|
||||||
Github
|
|
||||||
Facebook
|
|
||||||
)
|
|
||||||
|
|
||||||
func (d SignupMethod) String() string {
|
|
||||||
return [...]string{
|
|
||||||
"basic_auth",
|
|
||||||
"magic_link",
|
|
||||||
"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]
|
|
||||||
}
|
|
195
server/env.go
195
server/env.go
@@ -1,195 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"flag"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
|
||||||
"github.com/joho/godotenv"
|
|
||||||
)
|
|
||||||
|
|
||||||
// build variables
|
|
||||||
var (
|
|
||||||
Version string
|
|
||||||
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() {
|
|
||||||
envPath := `.env`
|
|
||||||
ARG_DB_URL = flag.String("database_url", "", "Database connection string")
|
|
||||||
ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite")
|
|
||||||
ARG_AUTHORIZER_URL = flag.String("authorizer_url", "", "URL for authorizer instance, eg: https://xyz.herokuapp.com")
|
|
||||||
ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
|
|
||||||
|
|
||||||
flag.Parse()
|
|
||||||
if *ARG_ENV_FILE != "" {
|
|
||||||
envPath = *ARG_ENV_FILE
|
|
||||||
}
|
|
||||||
|
|
||||||
err := godotenv.Load(envPath)
|
|
||||||
if err != nil {
|
|
||||||
log.Println("Error loading .env file")
|
|
||||||
}
|
|
||||||
|
|
||||||
constants.VERSION = Version
|
|
||||||
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
|
|
||||||
constants.ENV = os.Getenv("ENV")
|
|
||||||
constants.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
|
|
||||||
constants.DATABASE_URL = os.Getenv("DATABASE_URL")
|
|
||||||
constants.SMTP_HOST = os.Getenv("SMTP_HOST")
|
|
||||||
constants.SMTP_PORT = os.Getenv("SMTP_PORT")
|
|
||||||
constants.SENDER_EMAIL = os.Getenv("SENDER_EMAIL")
|
|
||||||
constants.SENDER_PASSWORD = os.Getenv("SENDER_PASSWORD")
|
|
||||||
constants.JWT_SECRET = os.Getenv("JWT_SECRET")
|
|
||||||
constants.JWT_TYPE = os.Getenv("JWT_TYPE")
|
|
||||||
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
|
|
||||||
constants.PORT = os.Getenv("PORT")
|
|
||||||
constants.REDIS_URL = os.Getenv("REDIS_URL")
|
|
||||||
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME")
|
|
||||||
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
|
|
||||||
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
|
|
||||||
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
|
|
||||||
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
|
|
||||||
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
|
|
||||||
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
|
|
||||||
constants.TWITTER_CLIENT_ID = os.Getenv("TWITTER_CLIENT_ID")
|
|
||||||
constants.TWITTER_CLIENT_SECRET = os.Getenv("TWITTER_CLIENT_SECRET")
|
|
||||||
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
|
|
||||||
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION")
|
|
||||||
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION")
|
|
||||||
constants.DISABLE_MAGIC_LOGIN = os.Getenv("DISABLE_MAGIC_LOGIN")
|
|
||||||
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
|
|
||||||
|
|
||||||
if constants.ADMIN_SECRET == "" {
|
|
||||||
panic("root admin secret is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.ENV == "" {
|
|
||||||
constants.ENV = "production"
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.ENV == "production" {
|
|
||||||
constants.IS_PROD = true
|
|
||||||
os.Setenv("GIN_MODE", "release")
|
|
||||||
} else {
|
|
||||||
constants.IS_PROD = false
|
|
||||||
}
|
|
||||||
|
|
||||||
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
|
|
||||||
allowedOrigins := []string{}
|
|
||||||
for _, val := range allowedOriginsSplit {
|
|
||||||
trimVal := strings.TrimSpace(val)
|
|
||||||
if trimVal != "" {
|
|
||||||
allowedOrigins = append(allowedOrigins, trimVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if len(allowedOrigins) == 0 {
|
|
||||||
allowedOrigins = []string{"*"}
|
|
||||||
}
|
|
||||||
constants.ALLOWED_ORIGINS = allowedOrigins
|
|
||||||
|
|
||||||
if *ARG_AUTHORIZER_URL != "" {
|
|
||||||
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ARG_DB_URL != "" {
|
|
||||||
constants.DATABASE_URL = *ARG_DB_URL
|
|
||||||
}
|
|
||||||
|
|
||||||
if *ARG_DB_TYPE != "" {
|
|
||||||
constants.DATABASE_TYPE = *ARG_DB_TYPE
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.DATABASE_URL == "" {
|
|
||||||
panic("Database url is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.DATABASE_TYPE == "" {
|
|
||||||
panic("Database type is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.JWT_TYPE == "" {
|
|
||||||
constants.JWT_TYPE = "HS256"
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.COOKIE_NAME == "" {
|
|
||||||
constants.COOKIE_NAME = "authorizer"
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.DISABLE_BASIC_AUTHENTICATION == "" {
|
|
||||||
constants.DISABLE_BASIC_AUTHENTICATION = "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.DISABLE_MAGIC_LOGIN == "" {
|
|
||||||
constants.DISABLE_MAGIC_LOGIN = "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
if constants.SMTP_HOST == "" || constants.SENDER_EMAIL == "" || constants.SENDER_PASSWORD == "" {
|
|
||||||
constants.DISABLE_EMAIL_VERIFICATION = "true"
|
|
||||||
} else if constants.DISABLE_EMAIL_VERIFICATION == "" {
|
|
||||||
constants.DISABLE_EMAIL_VERIFICATION = "false"
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("=> disable email verification:", constants.DISABLE_EMAIL_VERIFICATION)
|
|
||||||
|
|
||||||
rolesSplit := strings.Split(os.Getenv("ROLES"), ",")
|
|
||||||
roles := []string{}
|
|
||||||
if len(rolesSplit) == 0 {
|
|
||||||
roles = []string{"user"}
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultRoleSplit := strings.Split(os.Getenv("DEFAULT_ROLES"), ",")
|
|
||||||
defaultRoles := []string{}
|
|
||||||
|
|
||||||
if len(defaultRoleSplit) == 0 {
|
|
||||||
defaultRoles = []string{"user"}
|
|
||||||
}
|
|
||||||
|
|
||||||
protectedRolesSplit := strings.Split(os.Getenv("PROTECTED_ROLES"), ",")
|
|
||||||
protectedRoles := []string{}
|
|
||||||
|
|
||||||
if len(protectedRolesSplit) > 0 {
|
|
||||||
for _, val := range protectedRolesSplit {
|
|
||||||
trimVal := strings.TrimSpace(val)
|
|
||||||
protectedRoles = append(protectedRoles, trimVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, val := range rolesSplit {
|
|
||||||
trimVal := strings.TrimSpace(val)
|
|
||||||
if trimVal != "" {
|
|
||||||
roles = append(roles, trimVal)
|
|
||||||
}
|
|
||||||
|
|
||||||
if utils.StringSliceContains(defaultRoleSplit, trimVal) {
|
|
||||||
defaultRoles = append(defaultRoles, trimVal)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRoleSplit) > 0 {
|
|
||||||
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
|
|
||||||
}
|
|
||||||
|
|
||||||
constants.ROLES = roles
|
|
||||||
constants.DEFAULT_ROLES = defaultRoles
|
|
||||||
constants.PROTECTED_ROLES = protectedRoles
|
|
||||||
|
|
||||||
if constants.JWT_ROLE_CLAIM == "" {
|
|
||||||
constants.JWT_ROLE_CLAIM = "role"
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Getenv("ORGANIZATION_NAME") != "" {
|
|
||||||
constants.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME")
|
|
||||||
}
|
|
||||||
|
|
||||||
if os.Getenv("ORGANIZATION_LOGO") != "" {
|
|
||||||
constants.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO")
|
|
||||||
}
|
|
||||||
}
|
|
283
server/env/env.go
vendored
Normal file
283
server/env/env.go
vendored
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
package env
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
"github.com/authorizerdev/authorizer/server/envstore"
|
||||||
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/joho/godotenv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO move this to env store
|
||||||
|
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
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitEnv to initialize EnvData and through error if required env are not present
|
||||||
|
func InitEnv() {
|
||||||
|
// get clone of current store
|
||||||
|
envData := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyEnv] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyEnv] = os.Getenv("ENV")
|
||||||
|
if envData.StringEnv[constants.EnvKeyEnv] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyEnv] = "production"
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyEnv] == "production" {
|
||||||
|
envData.BoolEnv[constants.EnvKeyIsProd] = true
|
||||||
|
os.Setenv("GIN_MODE", "release")
|
||||||
|
} else {
|
||||||
|
envData.BoolEnv[constants.EnvKeyIsProd] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// set authorizer url to empty string so that fresh url is obtained with every server start
|
||||||
|
envData.StringEnv[constants.EnvKeyAuthorizerURL] = ""
|
||||||
|
if envData.StringEnv[constants.EnvKeyAppURL] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyEnvPath] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyEnvPath] = `.env`
|
||||||
|
}
|
||||||
|
|
||||||
|
if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyEnvPath] = *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 ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyDatabaseType] = *ARG_DB_TYPE
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyDatabaseType] == "" {
|
||||||
|
panic("DATABASE_TYPE is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyDatabaseURL] = os.Getenv("DATABASE_URL")
|
||||||
|
|
||||||
|
if ARG_DB_URL != nil && *ARG_DB_URL != "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyDatabaseURL] = *ARG_DB_URL
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyDatabaseURL] == "" {
|
||||||
|
panic("DATABASE_URL is required")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyDatabaseName] = os.Getenv("DATABASE_NAME")
|
||||||
|
if envData.StringEnv[constants.EnvKeyDatabaseName] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyDatabaseName] = "authorizer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeySmtpHost] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeySmtpHost] = os.Getenv("SMTP_HOST")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeySmtpPort] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeySmtpPort] = os.Getenv("SMTP_PORT")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeySmtpUsername] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeySmtpUsername] = os.Getenv("SMTP_USERNAME")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeySmtpPassword] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeySmtpPassword] = os.Getenv("SMTP_PASSWORD")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeySenderEmail] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeySenderEmail] = os.Getenv("SENDER_EMAIL")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtSecret] = os.Getenv("JWT_SECRET")
|
||||||
|
if envData.StringEnv[constants.EnvKeyJwtSecret] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtSecret] = uuid.New().String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtType] = os.Getenv("JWT_TYPE")
|
||||||
|
if envData.StringEnv[constants.EnvKeyJwtType] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtType] = "HS256"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtRoleClaim] = os.Getenv("JWT_ROLE_CLAIM")
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyJwtRoleClaim] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyJwtRoleClaim] = "role"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyRedisURL] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyRedisURL] = os.Getenv("REDIS_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyCookieName] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyCookieName] = os.Getenv("COOKIE_NAME")
|
||||||
|
if envData.StringEnv[constants.EnvKeyCookieName] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyCookieName] = "authorizer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyGoogleClientID] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyGoogleClientID] = os.Getenv("GOOGLE_CLIENT_ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyGoogleClientSecret] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyGoogleClientSecret] = os.Getenv("GOOGLE_CLIENT_SECRET")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyGithubClientID] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyGithubClientID] = os.Getenv("GITHUB_CLIENT_ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyGithubClientSecret] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyGithubClientSecret] = os.Getenv("GITHUB_CLIENT_SECRET")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyFacebookClientID] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyFacebookClientID] = os.Getenv("FACEBOOK_CLIENT_ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyFacebookClientSecret] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyFacebookClientSecret] = os.Getenv("FACEBOOK_CLIENT_SECRET")
|
||||||
|
}
|
||||||
|
|
||||||
|
if envData.StringEnv[constants.EnvKeyResetPasswordURL] == "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyResetPasswordURL] = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
|
||||||
|
}
|
||||||
|
|
||||||
|
envData.BoolEnv[constants.EnvKeyDisableBasicAuthentication] = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
|
||||||
|
envData.BoolEnv[constants.EnvKeyDisableEmailVerification] = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
|
||||||
|
envData.BoolEnv[constants.EnvKeyDisableMagicLinkLogin] = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
|
||||||
|
envData.BoolEnv[constants.EnvKeyDisableLoginPage] = os.Getenv("DISABLE_LOGIN_PAGE") == "true"
|
||||||
|
|
||||||
|
// no need to add nil check as its already done above
|
||||||
|
if envData.StringEnv[constants.EnvKeySmtpHost] == "" || envData.StringEnv[constants.EnvKeySmtpUsername] == "" || envData.StringEnv[constants.EnvKeySmtpPassword] == "" || envData.StringEnv[constants.EnvKeySenderEmail] == "" && envData.StringEnv[constants.EnvKeySmtpPort] == "" {
|
||||||
|
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"), ",")
|
||||||
|
allowedOrigins := []string{}
|
||||||
|
hasWildCard := false
|
||||||
|
|
||||||
|
for _, val := range allowedOriginsSplit {
|
||||||
|
trimVal := strings.TrimSpace(val)
|
||||||
|
if trimVal != "" {
|
||||||
|
if trimVal != "*" {
|
||||||
|
host, port := utils.GetHostParts(trimVal)
|
||||||
|
allowedOrigins = append(allowedOrigins, host+":"+port)
|
||||||
|
} else {
|
||||||
|
hasWildCard = true
|
||||||
|
allowedOrigins = append(allowedOrigins, trimVal)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allowedOrigins) > 1 && hasWildCard {
|
||||||
|
allowedOrigins = []string{"*"}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(allowedOrigins) == 0 {
|
||||||
|
allowedOrigins = []string{"*"}
|
||||||
|
}
|
||||||
|
|
||||||
|
envData.SliceEnv[constants.EnvKeyAllowedOrigins] = allowedOrigins
|
||||||
|
|
||||||
|
rolesEnv := strings.TrimSpace(os.Getenv("ROLES"))
|
||||||
|
rolesSplit := strings.Split(rolesEnv, ",")
|
||||||
|
roles := []string{}
|
||||||
|
if len(rolesEnv) == 0 {
|
||||||
|
roles = []string{"user"}
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultRolesEnv := strings.TrimSpace(os.Getenv("DEFAULT_ROLES"))
|
||||||
|
defaultRoleSplit := strings.Split(defaultRolesEnv, ",")
|
||||||
|
defaultRoles := []string{}
|
||||||
|
|
||||||
|
if len(defaultRolesEnv) == 0 {
|
||||||
|
defaultRoles = []string{"user"}
|
||||||
|
}
|
||||||
|
|
||||||
|
protectedRolesEnv := strings.TrimSpace(os.Getenv("PROTECTED_ROLES"))
|
||||||
|
protectedRolesSplit := strings.Split(protectedRolesEnv, ",")
|
||||||
|
protectedRoles := []string{}
|
||||||
|
|
||||||
|
if len(protectedRolesEnv) > 0 {
|
||||||
|
for _, val := range protectedRolesSplit {
|
||||||
|
trimVal := strings.TrimSpace(val)
|
||||||
|
protectedRoles = append(protectedRoles, trimVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, val := range rolesSplit {
|
||||||
|
trimVal := strings.TrimSpace(val)
|
||||||
|
if trimVal != "" {
|
||||||
|
roles = append(roles, trimVal)
|
||||||
|
}
|
||||||
|
|
||||||
|
if utils.StringSliceContains(defaultRoleSplit, trimVal) {
|
||||||
|
defaultRoles = append(defaultRoles, trimVal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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`)
|
||||||
|
}
|
||||||
|
|
||||||
|
envData.SliceEnv[constants.EnvKeyRoles] = roles
|
||||||
|
envData.SliceEnv[constants.EnvKeyDefaultRoles] = defaultRoles
|
||||||
|
envData.SliceEnv[constants.EnvKeyProtectedRoles] = protectedRoles
|
||||||
|
|
||||||
|
if os.Getenv("ORGANIZATION_NAME") != "" {
|
||||||
|
envData.StringEnv[constants.EnvKeyOrganizationName] = os.Getenv("ORGANIZATION_NAME")
|
||||||
|
}
|
||||||
|
|
||||||
|
if 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
|
||||||
|
}
|
102
server/envstore/store.go
Normal file
102
server/envstore/store.go
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
package envstore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
@@ -3,31 +3,38 @@ module github.com/authorizerdev/authorizer/server
|
|||||||
go 1.16
|
go 1.16
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/99designs/gqlgen v0.13.0
|
github.com/99designs/gqlgen v0.14.0
|
||||||
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
|
github.com/arangodb/go-driver v1.2.1
|
||||||
github.com/gin-contrib/location v0.0.2 // indirect
|
github.com/coreos/go-oidc/v3 v3.1.0
|
||||||
|
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
|
||||||
github.com/go-redis/redis/v8 v8.11.0
|
github.com/go-redis/redis/v8 v8.11.0
|
||||||
github.com/golang-jwt/jwt v3.2.1+incompatible
|
github.com/golang-jwt/jwt v3.2.2+incompatible
|
||||||
github.com/golang/protobuf v1.5.2 // indirect
|
github.com/golang/protobuf v1.5.2 // indirect
|
||||||
github.com/google/uuid v1.3.0
|
github.com/google/uuid v1.3.0
|
||||||
github.com/jackc/pgproto3/v2 v2.1.0 // indirect
|
|
||||||
github.com/joho/godotenv v1.3.0
|
github.com/joho/godotenv v1.3.0
|
||||||
github.com/json-iterator/go v1.1.11 // indirect
|
github.com/json-iterator/go v1.1.11 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.13 // indirect
|
github.com/mattn/go-isatty v0.0.13 // indirect
|
||||||
github.com/mattn/go-sqlite3 v1.14.7 // indirect
|
github.com/mitchellh/mapstructure v1.1.2 // indirect
|
||||||
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
|
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||||
|
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
|
||||||
|
github.com/stretchr/testify v1.7.0
|
||||||
github.com/ugorji/go v1.2.6 // indirect
|
github.com/ugorji/go v1.2.6 // indirect
|
||||||
github.com/vektah/gqlparser/v2 v2.1.0
|
github.com/vektah/gqlparser/v2 v2.2.0
|
||||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
go.mongodb.org/mongo-driver v1.8.1
|
||||||
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
|
||||||
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
|
||||||
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||||
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/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.1.1
|
gorm.io/driver/mysql v1.2.1
|
||||||
gorm.io/driver/postgres v1.1.0
|
gorm.io/driver/postgres v1.2.3
|
||||||
gorm.io/driver/sqlite v1.1.4
|
gorm.io/driver/sqlite v1.2.6
|
||||||
gorm.io/gorm v1.21.11
|
gorm.io/driver/sqlserver v1.2.1
|
||||||
|
gorm.io/gorm v1.22.4
|
||||||
)
|
)
|
||||||
|
577
server/go.sum
577
server/go.sum
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,26 +2,61 @@
|
|||||||
|
|
||||||
package model
|
package model
|
||||||
|
|
||||||
type AdminUpdateUserInput struct {
|
type AdminLoginInput struct {
|
||||||
ID string `json:"id"`
|
AdminSecret string `json:"admin_secret"`
|
||||||
Email *string `json:"email"`
|
}
|
||||||
FirstName *string `json:"firstName"`
|
|
||||||
LastName *string `json:"lastName"`
|
type AdminSignupInput struct {
|
||||||
Image *string `json:"image"`
|
AdminSecret string `json:"admin_secret"`
|
||||||
Roles []*string `json:"roles"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthResponse struct {
|
type AuthResponse struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
AccessToken *string `json:"accessToken"`
|
AccessToken *string `json:"access_token"`
|
||||||
AccessTokenExpiresAt *int64 `json:"accessTokenExpiresAt"`
|
ExpiresAt *int64 `json:"expires_at"`
|
||||||
User *User `json:"user"`
|
User *User `json:"user"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type DeleteUserInput struct {
|
type DeleteUserInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Env struct {
|
||||||
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
|
DatabaseType *string `json:"DATABASE_TYPE"`
|
||||||
|
DatabaseURL *string `json:"DATABASE_URL"`
|
||||||
|
DatabaseName *string `json:"DATABASE_NAME"`
|
||||||
|
SMTPHost *string `json:"SMTP_HOST"`
|
||||||
|
SMTPPort *string `json:"SMTP_PORT"`
|
||||||
|
SMTPUsername *string `json:"SMTP_USERNAME"`
|
||||||
|
SMTPPassword *string `json:"SMTP_PASSWORD"`
|
||||||
|
SenderEmail *string `json:"SENDER_EMAIL"`
|
||||||
|
JwtType *string `json:"JWT_TYPE"`
|
||||||
|
JwtSecret *string `json:"JWT_SECRET"`
|
||||||
|
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
||||||
|
AuthorizerURL *string `json:"AUTHORIZER_URL"`
|
||||||
|
AppURL *string `json:"APP_URL"`
|
||||||
|
RedisURL *string `json:"REDIS_URL"`
|
||||||
|
CookieName *string `json:"COOKIE_NAME"`
|
||||||
|
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
|
||||||
|
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
|
||||||
|
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
||||||
|
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
||||||
|
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
||||||
|
Roles []string `json:"ROLES"`
|
||||||
|
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
||||||
|
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
||||||
|
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
|
||||||
|
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
|
||||||
|
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
|
||||||
|
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
|
||||||
|
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
|
||||||
|
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
|
||||||
|
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
|
||||||
|
OrganizationName *string `json:"ORGANIZATION_NAME"`
|
||||||
|
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
|
||||||
|
}
|
||||||
|
|
||||||
type Error struct {
|
type Error struct {
|
||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
@@ -37,30 +72,30 @@ type LoginInput struct {
|
|||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type MagicLoginInput struct {
|
type MagicLinkLoginInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Meta struct {
|
type Meta struct {
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
IsGoogleLoginEnabled bool `json:"isGoogleLoginEnabled"`
|
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
|
||||||
IsFacebookLoginEnabled bool `json:"isFacebookLoginEnabled"`
|
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
|
||||||
IsTwitterLoginEnabled bool `json:"isTwitterLoginEnabled"`
|
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
|
||||||
IsGithubLoginEnabled bool `json:"isGithubLoginEnabled"`
|
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
|
||||||
IsEmailVerificationEnabled bool `json:"isEmailVerificationEnabled"`
|
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
|
||||||
IsBasicAuthenticationEnabled bool `json:"isBasicAuthenticationEnabled"`
|
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
|
||||||
IsMagicLoginEnabled bool `json:"isMagicLoginEnabled"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResendVerifyEmailInput struct {
|
type ResendVerifyEmailInput struct {
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
Identifier string `json:"identifier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResetPasswordInput struct {
|
type ResetPasswordInput struct {
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
ConfirmPassword string `json:"confirmPassword"`
|
ConfirmPassword string `json:"confirm_password"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Response struct {
|
type Response struct {
|
||||||
@@ -68,36 +103,104 @@ type Response struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type SignUpInput struct {
|
type SignUpInput struct {
|
||||||
FirstName *string `json:"firstName"`
|
|
||||||
LastName *string `json:"lastName"`
|
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
|
GivenName *string `json:"given_name"`
|
||||||
|
FamilyName *string `json:"family_name"`
|
||||||
|
MiddleName *string `json:"middle_name"`
|
||||||
|
Nickname *string `json:"nickname"`
|
||||||
|
Gender *string `json:"gender"`
|
||||||
|
Birthdate *string `json:"birthdate"`
|
||||||
|
PhoneNumber *string `json:"phone_number"`
|
||||||
|
Picture *string `json:"picture"`
|
||||||
Password string `json:"password"`
|
Password string `json:"password"`
|
||||||
ConfirmPassword string `json:"confirmPassword"`
|
ConfirmPassword string `json:"confirm_password"`
|
||||||
Image *string `json:"image"`
|
|
||||||
Roles []string `json:"roles"`
|
Roles []string `json:"roles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateEnvInput struct {
|
||||||
|
AdminSecret *string `json:"ADMIN_SECRET"`
|
||||||
|
OldAdminSecret *string `json:"OLD_ADMIN_SECRET"`
|
||||||
|
DatabaseType *string `json:"DATABASE_TYPE"`
|
||||||
|
DatabaseURL *string `json:"DATABASE_URL"`
|
||||||
|
DatabaseName *string `json:"DATABASE_NAME"`
|
||||||
|
SMTPHost *string `json:"SMTP_HOST"`
|
||||||
|
SMTPPort *string `json:"SMTP_PORT"`
|
||||||
|
SenderEmail *string `json:"SENDER_EMAIL"`
|
||||||
|
SenderPassword *string `json:"SENDER_PASSWORD"`
|
||||||
|
JwtType *string `json:"JWT_TYPE"`
|
||||||
|
JwtSecret *string `json:"JWT_SECRET"`
|
||||||
|
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
|
||||||
|
AuthorizerURL *string `json:"AUTHORIZER_URL"`
|
||||||
|
AppURL *string `json:"APP_URL"`
|
||||||
|
RedisURL *string `json:"REDIS_URL"`
|
||||||
|
CookieName *string `json:"COOKIE_NAME"`
|
||||||
|
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
|
||||||
|
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
|
||||||
|
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
|
||||||
|
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
|
||||||
|
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
|
||||||
|
Roles []string `json:"ROLES"`
|
||||||
|
ProtectedRoles []string `json:"PROTECTED_ROLES"`
|
||||||
|
DefaultRoles []string `json:"DEFAULT_ROLES"`
|
||||||
|
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
|
||||||
|
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
|
||||||
|
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
|
||||||
|
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
|
||||||
|
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
|
||||||
|
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
|
||||||
|
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
|
||||||
|
OrganizationName *string `json:"ORGANIZATION_NAME"`
|
||||||
|
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
|
||||||
|
}
|
||||||
|
|
||||||
type UpdateProfileInput struct {
|
type UpdateProfileInput struct {
|
||||||
OldPassword *string `json:"oldPassword"`
|
OldPassword *string `json:"old_password"`
|
||||||
NewPassword *string `json:"newPassword"`
|
NewPassword *string `json:"new_password"`
|
||||||
ConfirmNewPassword *string `json:"confirmNewPassword"`
|
ConfirmNewPassword *string `json:"confirm_new_password"`
|
||||||
FirstName *string `json:"firstName"`
|
|
||||||
LastName *string `json:"lastName"`
|
|
||||||
Image *string `json:"image"`
|
|
||||||
Email *string `json:"email"`
|
Email *string `json:"email"`
|
||||||
|
GivenName *string `json:"given_name"`
|
||||||
|
FamilyName *string `json:"family_name"`
|
||||||
|
MiddleName *string `json:"middle_name"`
|
||||||
|
Nickname *string `json:"nickname"`
|
||||||
|
Gender *string `json:"gender"`
|
||||||
|
Birthdate *string `json:"birthdate"`
|
||||||
|
PhoneNumber *string `json:"phone_number"`
|
||||||
|
Picture *string `json:"picture"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateUserInput struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Email *string `json:"email"`
|
||||||
|
EmailVerified *bool `json:"email_verified"`
|
||||||
|
GivenName *string `json:"given_name"`
|
||||||
|
FamilyName *string `json:"family_name"`
|
||||||
|
MiddleName *string `json:"middle_name"`
|
||||||
|
Nickname *string `json:"nickname"`
|
||||||
|
Gender *string `json:"gender"`
|
||||||
|
Birthdate *string `json:"birthdate"`
|
||||||
|
PhoneNumber *string `json:"phone_number"`
|
||||||
|
Picture *string `json:"picture"`
|
||||||
|
Roles []*string `json:"roles"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
SignupMethod string `json:"signupMethod"`
|
EmailVerified bool `json:"email_verified"`
|
||||||
FirstName *string `json:"firstName"`
|
SignupMethods string `json:"signup_methods"`
|
||||||
LastName *string `json:"lastName"`
|
GivenName *string `json:"given_name"`
|
||||||
EmailVerifiedAt *int64 `json:"emailVerifiedAt"`
|
FamilyName *string `json:"family_name"`
|
||||||
Image *string `json:"image"`
|
MiddleName *string `json:"middle_name"`
|
||||||
CreatedAt *int64 `json:"createdAt"`
|
Nickname *string `json:"nickname"`
|
||||||
UpdatedAt *int64 `json:"updatedAt"`
|
PreferredUsername *string `json:"preferred_username"`
|
||||||
Roles []string `json:"roles"`
|
Gender *string `json:"gender"`
|
||||||
|
Birthdate *string `json:"birthdate"`
|
||||||
|
PhoneNumber *string `json:"phone_number"`
|
||||||
|
PhoneNumberVerified *bool `json:"phone_number_verified"`
|
||||||
|
Picture *string `json:"picture"`
|
||||||
|
Roles []string `json:"roles"`
|
||||||
|
CreatedAt *int64 `json:"created_at"`
|
||||||
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerificationRequest struct {
|
type VerificationRequest struct {
|
||||||
@@ -106,8 +209,8 @@ type VerificationRequest struct {
|
|||||||
Token *string `json:"token"`
|
Token *string `json:"token"`
|
||||||
Email *string `json:"email"`
|
Email *string `json:"email"`
|
||||||
Expires *int64 `json:"expires"`
|
Expires *int64 `json:"expires"`
|
||||||
CreatedAt *int64 `json:"createdAt"`
|
CreatedAt *int64 `json:"created_at"`
|
||||||
UpdatedAt *int64 `json:"updatedAt"`
|
UpdatedAt *int64 `json:"updated_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerifyEmailInput struct {
|
type VerifyEmailInput struct {
|
||||||
|
@@ -7,26 +7,33 @@ scalar Any
|
|||||||
|
|
||||||
type Meta {
|
type Meta {
|
||||||
version: String!
|
version: String!
|
||||||
isGoogleLoginEnabled: Boolean!
|
is_google_login_enabled: Boolean!
|
||||||
isFacebookLoginEnabled: Boolean!
|
is_facebook_login_enabled: Boolean!
|
||||||
isTwitterLoginEnabled: Boolean!
|
is_github_login_enabled: Boolean!
|
||||||
isGithubLoginEnabled: Boolean!
|
is_email_verification_enabled: Boolean!
|
||||||
isEmailVerificationEnabled: Boolean!
|
is_basic_authentication_enabled: Boolean!
|
||||||
isBasicAuthenticationEnabled: Boolean!
|
is_magic_link_login_enabled: Boolean!
|
||||||
isMagicLoginEnabled: Boolean!
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
id: ID!
|
id: ID!
|
||||||
email: String!
|
email: String!
|
||||||
signupMethod: String!
|
email_verified: Boolean!
|
||||||
firstName: String
|
signup_methods: String!
|
||||||
lastName: String
|
given_name: String
|
||||||
emailVerifiedAt: Int64
|
family_name: String
|
||||||
image: String
|
middle_name: String
|
||||||
createdAt: Int64
|
nickname: String
|
||||||
updatedAt: Int64
|
# defaults to email
|
||||||
|
preferred_username: String
|
||||||
|
gender: String
|
||||||
|
birthdate: String
|
||||||
|
phone_number: String
|
||||||
|
phone_number_verified: Boolean
|
||||||
|
picture: String
|
||||||
roles: [String!]!
|
roles: [String!]!
|
||||||
|
created_at: Int64
|
||||||
|
updated_at: Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type VerificationRequest {
|
type VerificationRequest {
|
||||||
@@ -35,8 +42,8 @@ type VerificationRequest {
|
|||||||
token: String
|
token: String
|
||||||
email: String
|
email: String
|
||||||
expires: Int64
|
expires: Int64
|
||||||
createdAt: Int64
|
created_at: Int64
|
||||||
updatedAt: Int64
|
updated_at: Int64
|
||||||
}
|
}
|
||||||
|
|
||||||
type Error {
|
type Error {
|
||||||
@@ -46,8 +53,8 @@ type Error {
|
|||||||
|
|
||||||
type AuthResponse {
|
type AuthResponse {
|
||||||
message: String!
|
message: String!
|
||||||
accessToken: String
|
access_token: String
|
||||||
accessTokenExpiresAt: Int64
|
expires_at: Int64
|
||||||
user: User
|
user: User
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,13 +62,98 @@ type Response {
|
|||||||
message: String!
|
message: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Env {
|
||||||
|
ADMIN_SECRET: String
|
||||||
|
DATABASE_TYPE: String
|
||||||
|
DATABASE_URL: String
|
||||||
|
DATABASE_NAME: String
|
||||||
|
SMTP_HOST: String
|
||||||
|
SMTP_PORT: String
|
||||||
|
SMTP_USERNAME: String
|
||||||
|
SMTP_PASSWORD: String
|
||||||
|
SENDER_EMAIL: String
|
||||||
|
JWT_TYPE: String
|
||||||
|
JWT_SECRET: String
|
||||||
|
ALLOWED_ORIGINS: [String!]
|
||||||
|
AUTHORIZER_URL: String
|
||||||
|
APP_URL: String
|
||||||
|
REDIS_URL: String
|
||||||
|
COOKIE_NAME: String
|
||||||
|
RESET_PASSWORD_URL: String
|
||||||
|
DISABLE_EMAIL_VERIFICATION: Boolean
|
||||||
|
DISABLE_BASIC_AUTHENTICATION: Boolean
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN: Boolean
|
||||||
|
DISABLE_LOGIN_PAGE: Boolean
|
||||||
|
ROLES: [String!]
|
||||||
|
PROTECTED_ROLES: [String!]
|
||||||
|
DEFAULT_ROLES: [String!]
|
||||||
|
JWT_ROLE_CLAIM: String
|
||||||
|
GOOGLE_CLIENT_ID: String
|
||||||
|
GOOGLE_CLIENT_SECRET: String
|
||||||
|
GITHUB_CLIENT_ID: String
|
||||||
|
GITHUB_CLIENT_SECRET: String
|
||||||
|
FACEBOOK_CLIENT_ID: String
|
||||||
|
FACEBOOK_CLIENT_SECRET: String
|
||||||
|
ORGANIZATION_NAME: String
|
||||||
|
ORGANIZATION_LOGO: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input UpdateEnvInput {
|
||||||
|
ADMIN_SECRET: String
|
||||||
|
OLD_ADMIN_SECRET: String
|
||||||
|
DATABASE_TYPE: String
|
||||||
|
DATABASE_URL: String
|
||||||
|
DATABASE_NAME: String
|
||||||
|
SMTP_HOST: String
|
||||||
|
SMTP_PORT: String
|
||||||
|
SENDER_EMAIL: String
|
||||||
|
SENDER_PASSWORD: String
|
||||||
|
JWT_TYPE: String
|
||||||
|
JWT_SECRET: String
|
||||||
|
ALLOWED_ORIGINS: [String!]
|
||||||
|
AUTHORIZER_URL: String
|
||||||
|
APP_URL: String
|
||||||
|
REDIS_URL: String
|
||||||
|
COOKIE_NAME: String
|
||||||
|
RESET_PASSWORD_URL: String
|
||||||
|
DISABLE_EMAIL_VERIFICATION: Boolean
|
||||||
|
DISABLE_BASIC_AUTHENTICATION: Boolean
|
||||||
|
DISABLE_MAGIC_LINK_LOGIN: Boolean
|
||||||
|
DISABLE_LOGIN_PAGE: Boolean
|
||||||
|
ROLES: [String!]
|
||||||
|
PROTECTED_ROLES: [String!]
|
||||||
|
DEFAULT_ROLES: [String!]
|
||||||
|
JWT_ROLE_CLAIM: String
|
||||||
|
GOOGLE_CLIENT_ID: String
|
||||||
|
GOOGLE_CLIENT_SECRET: String
|
||||||
|
GITHUB_CLIENT_ID: String
|
||||||
|
GITHUB_CLIENT_SECRET: String
|
||||||
|
FACEBOOK_CLIENT_ID: String
|
||||||
|
FACEBOOK_CLIENT_SECRET: String
|
||||||
|
ORGANIZATION_NAME: String
|
||||||
|
ORGANIZATION_LOGO: String
|
||||||
|
}
|
||||||
|
|
||||||
|
input AdminLoginInput {
|
||||||
|
admin_secret: String!
|
||||||
|
}
|
||||||
|
|
||||||
|
input AdminSignupInput {
|
||||||
|
admin_secret: String!
|
||||||
|
}
|
||||||
|
|
||||||
input SignUpInput {
|
input SignUpInput {
|
||||||
firstName: String
|
|
||||||
lastName: String
|
|
||||||
email: String!
|
email: String!
|
||||||
|
given_name: String
|
||||||
|
family_name: String
|
||||||
|
middle_name: String
|
||||||
|
nickname: String
|
||||||
|
gender: String
|
||||||
|
birthdate: String
|
||||||
|
phone_number: String
|
||||||
|
picture: String
|
||||||
password: String!
|
password: String!
|
||||||
confirmPassword: String!
|
confirm_password: String!
|
||||||
image: String
|
|
||||||
roles: [String!]
|
roles: [String!]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,25 +169,36 @@ input VerifyEmailInput {
|
|||||||
|
|
||||||
input ResendVerifyEmailInput {
|
input ResendVerifyEmailInput {
|
||||||
email: String!
|
email: String!
|
||||||
|
identifier: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
input UpdateProfileInput {
|
input UpdateProfileInput {
|
||||||
oldPassword: String
|
old_password: String
|
||||||
newPassword: String
|
new_password: String
|
||||||
confirmNewPassword: String
|
confirm_new_password: String
|
||||||
firstName: String
|
|
||||||
lastName: String
|
|
||||||
image: String
|
|
||||||
email: String
|
email: String
|
||||||
# roles: [String]
|
given_name: String
|
||||||
|
family_name: String
|
||||||
|
middle_name: String
|
||||||
|
nickname: String
|
||||||
|
gender: String
|
||||||
|
birthdate: String
|
||||||
|
phone_number: String
|
||||||
|
picture: String
|
||||||
}
|
}
|
||||||
|
|
||||||
input AdminUpdateUserInput {
|
input UpdateUserInput {
|
||||||
id: ID!
|
id: ID!
|
||||||
email: String
|
email: String
|
||||||
firstName: String
|
email_verified: Boolean
|
||||||
lastName: String
|
given_name: String
|
||||||
image: String
|
family_name: String
|
||||||
|
middle_name: String
|
||||||
|
nickname: String
|
||||||
|
gender: String
|
||||||
|
birthdate: String
|
||||||
|
phone_number: String
|
||||||
|
picture: String
|
||||||
roles: [String]
|
roles: [String]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,14 +209,14 @@ input ForgotPasswordInput {
|
|||||||
input ResetPasswordInput {
|
input ResetPasswordInput {
|
||||||
token: String!
|
token: String!
|
||||||
password: String!
|
password: String!
|
||||||
confirmPassword: String!
|
confirm_password: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
input DeleteUserInput {
|
input DeleteUserInput {
|
||||||
email: String!
|
email: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
input MagicLoginInput {
|
input MagicLinkLoginInput {
|
||||||
email: String!
|
email: String!
|
||||||
roles: [String!]
|
roles: [String!]
|
||||||
}
|
}
|
||||||
@@ -121,21 +224,29 @@ input MagicLoginInput {
|
|||||||
type Mutation {
|
type Mutation {
|
||||||
signup(params: SignUpInput!): AuthResponse!
|
signup(params: SignUpInput!): AuthResponse!
|
||||||
login(params: LoginInput!): AuthResponse!
|
login(params: LoginInput!): AuthResponse!
|
||||||
magicLogin(params: MagicLoginInput!): Response!
|
magic_link_login(params: MagicLinkLoginInput!): Response!
|
||||||
logout: Response!
|
logout: Response!
|
||||||
updateProfile(params: UpdateProfileInput!): Response!
|
update_profile(params: UpdateProfileInput!): Response!
|
||||||
adminUpdateUser(params: AdminUpdateUserInput!): User!
|
verify_email(params: VerifyEmailInput!): AuthResponse!
|
||||||
verifyEmail(params: VerifyEmailInput!): AuthResponse!
|
resend_verify_email(params: ResendVerifyEmailInput!): Response!
|
||||||
resendVerifyEmail(params: ResendVerifyEmailInput!): Response!
|
forgot_password(params: ForgotPasswordInput!): Response!
|
||||||
forgotPassword(params: ForgotPasswordInput!): Response!
|
reset_password(params: ResetPasswordInput!): Response!
|
||||||
resetPassword(params: ResetPasswordInput!): Response!
|
# admin only apis
|
||||||
deleteUser(params: DeleteUserInput!): Response!
|
_delete_user(params: DeleteUserInput!): Response!
|
||||||
|
_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!
|
||||||
users: [User!]!
|
session(roles: [String!]): AuthResponse!
|
||||||
token(roles: [String!]): AuthResponse
|
|
||||||
profile: User!
|
profile: User!
|
||||||
verificationRequests: [VerificationRequest!]!
|
# admin only apis
|
||||||
|
_users: [User!]!
|
||||||
|
_verification_requests: [VerificationRequest!]!
|
||||||
|
_admin_session: Response!
|
||||||
|
_env: Env!
|
||||||
}
|
}
|
||||||
|
@@ -12,67 +12,91 @@ 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) MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Response, error) {
|
func (r *mutationResolver) MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
|
||||||
return resolvers.MagicLogin(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) AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error) {
|
|
||||||
return resolvers.AdminUpdateUser(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) {
|
||||||
|
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) Users(ctx context.Context) ([]*model.User, error) {
|
func (r *queryResolver) Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
|
||||||
return resolvers.Users(ctx)
|
return resolvers.SessionResolver(ctx, roles)
|
||||||
}
|
|
||||||
|
|
||||||
func (r *queryResolver) Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
|
|
||||||
return resolvers.Token(ctx, roles)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
|
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.
|
||||||
|
@@ -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
|
||||||
@@ -49,7 +46,7 @@ func AppHandler() gin.HandlerFunc {
|
|||||||
stateObj.RedirectURL = strings.TrimSuffix(stateObj.RedirectURL, "/")
|
stateObj.RedirectURL = strings.TrimSuffix(stateObj.RedirectURL, "/")
|
||||||
|
|
||||||
// validate redirect url with allowed origins
|
// validate redirect url with allowed origins
|
||||||
if !utils.IsValidRedirectURL(stateObj.RedirectURL) {
|
if !utils.IsValidOrigin(stateObj.RedirectURL) {
|
||||||
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
||||||
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
|
||||||
|
@@ -12,7 +12,8 @@ import (
|
|||||||
|
|
||||||
"github.com/authorizerdev/authorizer/server/constants"
|
"github.com/authorizerdev/authorizer/server/constants"
|
||||||
"github.com/authorizerdev/authorizer/server/db"
|
"github.com/authorizerdev/authorizer/server/db"
|
||||||
"github.com/authorizerdev/authorizer/server/enum"
|
"github.com/authorizerdev/authorizer/server/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/session"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
@@ -21,8 +22,133 @@ import (
|
|||||||
"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 := 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 := 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)
|
||||||
|
userIdStr := fmt.Sprintf("%v", user.ID)
|
||||||
|
refreshToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeRefreshToken, inputRoles)
|
||||||
|
|
||||||
|
accessToken, _, _ := utils.CreateAuthToken(user, constants.TokenTypeAccessToken, inputRoles)
|
||||||
|
utils.SetCookie(c, accessToken)
|
||||||
|
session.SetUserSession(userIdStr, accessToken, refreshToken)
|
||||||
|
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 {
|
||||||
@@ -43,31 +169,15 @@ func processGoogleUserInfo(code string) (db.User, error) {
|
|||||||
return user, fmt.Errorf("unable to verify id_token: %s", err.Error())
|
return user, fmt.Errorf("unable to verify id_token: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract custom claims
|
if err := idToken.Claims(&user); err != nil {
|
||||||
var claims struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
Picture string `json:"picture"`
|
|
||||||
GivenName string `json:"given_name"`
|
|
||||||
FamilyName string `json:"family_name"`
|
|
||||||
Verified bool `json:"email_verified"`
|
|
||||||
}
|
|
||||||
if err := idToken.Claims(&claims); err != nil {
|
|
||||||
return user, fmt.Errorf("unable to extract claims")
|
return user, fmt.Errorf("unable to extract claims")
|
||||||
}
|
}
|
||||||
|
|
||||||
user = db.User{
|
|
||||||
FirstName: claims.GivenName,
|
|
||||||
LastName: claims.FamilyName,
|
|
||||||
Image: claims.Picture,
|
|
||||||
Email: claims.Email,
|
|
||||||
EmailVerifiedAt: time.Now().Unix(),
|
|
||||||
}
|
|
||||||
|
|
||||||
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())
|
||||||
@@ -104,19 +214,21 @@ func processGithubUserInfo(code string) (db.User, error) {
|
|||||||
if len(name) > 1 && strings.TrimSpace(name[1]) != "" {
|
if len(name) > 1 && strings.TrimSpace(name[1]) != "" {
|
||||||
lastName = name[0]
|
lastName = name[0]
|
||||||
}
|
}
|
||||||
user = db.User{
|
|
||||||
FirstName: firstName,
|
picture := userRawData["avatar_url"]
|
||||||
LastName: lastName,
|
|
||||||
Image: userRawData["avatar_url"],
|
user = models.User{
|
||||||
Email: userRawData["email"],
|
GivenName: &firstName,
|
||||||
EmailVerifiedAt: time.Now().Unix(),
|
FamilyName: &lastName,
|
||||||
|
Picture: &picture,
|
||||||
|
Email: userRawData["email"],
|
||||||
}
|
}
|
||||||
|
|
||||||
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())
|
||||||
@@ -129,7 +241,7 @@ func processFacebookUserInfo(code string) (db.User, error) {
|
|||||||
|
|
||||||
response, err := client.Do(req)
|
response, err := client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("err:", err)
|
log.Println("error processing facebook user info:", err)
|
||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,140 +258,16 @@ func processFacebookUserInfo(code string) (db.User, error) {
|
|||||||
|
|
||||||
picObject := userRawData["picture"].(map[string]interface{})["data"]
|
picObject := userRawData["picture"].(map[string]interface{})["data"]
|
||||||
picDataObject := picObject.(map[string]interface{})
|
picDataObject := picObject.(map[string]interface{})
|
||||||
user = db.User{
|
firstName := fmt.Sprintf("%v", userRawData["first_name"])
|
||||||
FirstName: fmt.Sprintf("%v", userRawData["first_name"]),
|
lastName := fmt.Sprintf("%v", userRawData["last_name"])
|
||||||
LastName: fmt.Sprintf("%v", userRawData["last_name"]),
|
picture := fmt.Sprintf("%v", picDataObject["url"])
|
||||||
Image: fmt.Sprintf("%v", picDataObject["url"]),
|
|
||||||
Email: email,
|
user = models.User{
|
||||||
EmailVerifiedAt: time.Now().Unix(),
|
GivenName: &firstName,
|
||||||
|
FamilyName: &lastName,
|
||||||
|
Picture: &picture,
|
||||||
|
Email: email,
|
||||||
}
|
}
|
||||||
|
|
||||||
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.SignupMethod = 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, ",")
|
|
||||||
} else {
|
|
||||||
// user exists in db, check if method was google
|
|
||||||
// if not append google to existing signup method and save it
|
|
||||||
|
|
||||||
signupMethod := existingUser.SignupMethod
|
|
||||||
if !strings.Contains(signupMethod, provider) {
|
|
||||||
signupMethod = signupMethod + "," + provider
|
|
||||||
}
|
|
||||||
user.SignupMethod = 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, _ = db.Mgr.SaveUser(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)
|
|
||||||
go func() {
|
|
||||||
sessionData := db.Session{
|
|
||||||
UserID: user.ID,
|
|
||||||
UserAgent: utils.GetUserAgent(c.Request),
|
|
||||||
IP: utils.GetIP(c.Request),
|
|
||||||
}
|
|
||||||
|
|
||||||
db.Mgr.SaveSession(sessionData)
|
|
||||||
}()
|
|
||||||
|
|
||||||
c.Redirect(http.StatusTemporaryRedirect, redirectURL)
|
|
||||||
}
|
|
||||||
}
|
|
@@ -5,7 +5,7 @@ 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/session"
|
||||||
"github.com/authorizerdev/authorizer/server/utils"
|
"github.com/authorizerdev/authorizer/server/utils"
|
||||||
@@ -13,8 +13,7 @@ import (
|
|||||||
"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())
|
session.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())
|
session.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())
|
session.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:
|
@@ -5,6 +5,7 @@ import (
|
|||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// PlaygroundHandler is the handler for the /playground route
|
||||||
func PlaygroundHandler() gin.HandlerFunc {
|
func PlaygroundHandler() gin.HandlerFunc {
|
||||||
h := playground.Handler("GraphQL", "/graphql")
|
h := playground.Handler("GraphQL", "/graphql")
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user