Compare commits
104 Commits
0.1.0-beta
...
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 | ||
![]() |
1761f41691 | ||
![]() |
00565c8717 | ||
![]() |
74a551ae09 | ||
![]() |
cb5b02d777 | ||
![]() |
6ca37a0d50 | ||
![]() |
a9cf301344 | ||
![]() |
0305a719db | ||
![]() |
4269e2242c | ||
![]() |
71e4e35de6 | ||
![]() |
3aeb3b8d67 | ||
![]() |
e1951bfbe0 | ||
![]() |
1299ec5f9c | ||
![]() |
bc53974d2a | ||
![]() |
3ed5426467 | ||
![]() |
29251c8c20 | ||
![]() |
08b1f97ccb |
@@ -6,4 +6,6 @@ README.md
|
||||
ROADMAP.md
|
||||
build
|
||||
.env
|
||||
data.db
|
||||
data.db
|
||||
app/node_modules
|
||||
app/build
|
||||
|
@@ -2,10 +2,15 @@ ENV=production
|
||||
DATABASE_URL=data.db
|
||||
DATABASE_TYPE=sqlite
|
||||
ADMIN_SECRET=admin
|
||||
DISABLE_EMAIL_VERIFICATION=true
|
||||
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
|
||||
ROLES=user
|
||||
DEFAULT_ROLES=user
|
||||
PROTECTED_ROLES=admin
|
||||
JWT_ROLE_CLAIM=role
|
||||
JWT_ROLE_CLAIM=role
|
||||
CUSTOM_ACCESS_TOKEN_SCRIPT=function(user,tokenPayload){var data = tokenPayload;data.extra = {'x-extra-id': user.id};return data;}
|
182
.github/CONTRIBUTING.md
vendored
182
.github/CONTRIBUTING.md
vendored
@@ -10,7 +10,7 @@ We're so excited you're interested in helping with Authorizer! We are happy to h
|
||||
## Where to ask questions?
|
||||
|
||||
1. Check our [Github Issues](https://github.com/authorizerdev/authorizer/issues) to see if someone has already answered your question.
|
||||
2. Join our community on [Discord](https://discord.gg/WDvCxwkX) and feel free to ask us your questions
|
||||
2. Join our community on [Discord](https://discord.gg/Zv2D5h6kkK) and feel free to ask us your questions
|
||||
|
||||
As you gain experience with Authorizer, please help answer other people's questions! :pray:
|
||||
|
||||
@@ -19,7 +19,7 @@ As you gain experience with Authorizer, please help answer other people's questi
|
||||
You can get started by taking a look at our [Github issues](https://github.com/authorizerdev/authorizer/issues)
|
||||
If you find one that looks interesting and no one else is already working on it, comment on that issue and start contributing 🙂.
|
||||
|
||||
Please ask as many questions as you need, either directly in the issue or on [Discord](https://discord.gg/WDvCxwkX). We're happy to help!:raised_hands:
|
||||
Please ask as many questions as you need, either directly in the issue or on [Discord](https://discord.gg/Zv2D5h6kkK). We're happy to help!:raised_hands:
|
||||
|
||||
### Contributions that are ALWAYS welcome
|
||||
|
||||
@@ -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`
|
||||
> 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`
|
||||
|
||||
### 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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
38
.github/workflows/release.yaml
vendored
38
.github/workflows/release.yaml
vendored
@@ -8,26 +8,23 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- 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
|
||||
run: |
|
||||
sudo apt-get install build-essential wget zip gcc-mingw-w64 && \
|
||||
sudo apt-get remove --auto-remove golang-go && \
|
||||
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 && \
|
||||
echo "/usr/bin/x86_64-w64-mingw32-gcc" >> GITHUB_PATH && \
|
||||
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 && \
|
||||
sudo mv github-assets-uploader /usr/sbin/ && \
|
||||
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
|
||||
run: whereis go
|
||||
- name: Print Go Version
|
||||
@@ -36,19 +33,20 @@ jobs:
|
||||
run: echo VERSION=$(basename ${GITHUB_REF}) >> ${GITHUB_ENV}
|
||||
- name: Copy .env file
|
||||
run: mv .env.sample .env
|
||||
# - name: Package files for windows
|
||||
# run: |
|
||||
# make clean && \
|
||||
# CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
|
||||
# mv build/server build/server.exe && \
|
||||
# zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates
|
||||
- name: Package files for windows
|
||||
run: |
|
||||
make clean && \
|
||||
CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
|
||||
mv build/server build/server.exe && \
|
||||
zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates dashboard/build
|
||||
- name: Package files for linux
|
||||
run: |
|
||||
make clean && \
|
||||
CGO_ENABLED=1 make && \
|
||||
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates
|
||||
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates dashboard/build
|
||||
- name: Upload assets
|
||||
run: |
|
||||
github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \
|
||||
github-assets-uploader -f authorizer-${VERSION}-linux-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
|
||||
- name: Log in to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,6 +2,9 @@ server/server
|
||||
server/.env
|
||||
data
|
||||
app/node_modules
|
||||
app/build
|
||||
dashboard/node_modules
|
||||
dashboard/build
|
||||
build
|
||||
.env
|
||||
data.db
|
||||
|
22
Dockerfile
22
Dockerfile
@@ -1,5 +1,5 @@
|
||||
FROM golang:1.16-alpine as builder
|
||||
WORKDIR /app
|
||||
FROM golang:1.17-alpine as go-builder
|
||||
WORKDIR /authorizer
|
||||
COPY server server
|
||||
COPY Makefile .
|
||||
|
||||
@@ -11,11 +11,21 @@ RUN apk add build-base &&\
|
||||
make clean && make && \
|
||||
chmod 777 build/server
|
||||
|
||||
FROM alpine:latest
|
||||
RUN apk --no-cache add ca-certificates
|
||||
WORKDIR /root/
|
||||
FROM node:17-alpine3.12 as node-builder
|
||||
WORKDIR /authorizer
|
||||
COPY app app
|
||||
COPY dashboard dashboard
|
||||
COPY Makefile .
|
||||
RUN apk add build-base &&\
|
||||
make build-app && \
|
||||
make build-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 --from=builder /app/build build
|
||||
EXPOSE 8080
|
||||
CMD [ "./build/server" ]
|
||||
|
10
Makefile
10
Makefile
@@ -2,6 +2,12 @@ DEFAULT_VERSION=0.1.0-local
|
||||
VERSION := $(or $(VERSION),$(DEFAULT_VERSION))
|
||||
|
||||
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:
|
||||
rm -rf build
|
||||
rm -rf build
|
||||
test:
|
||||
cd server && go clean --testcache && go test -v ./test
|
||||
|
45
README.md
45
README.md
@@ -7,7 +7,7 @@
|
||||
Authorizer
|
||||
</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
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
- [Getting Started](#getting-started)
|
||||
- [Contributing](https://github.com/authorizerdev/authorizer/blob/main/.github/CONTRIBUTING.md)
|
||||
- [Docs](http://docs.authorizer.dev/)
|
||||
- [Join Community](https://discord.gg/2fXUQN3E)
|
||||
- [Join Community](https://discord.gg/Zv2D5h6kkK)
|
||||
|
||||
# Introduction
|
||||
|
||||
@@ -30,14 +30,10 @@
|
||||
- ✅ Forgot password flow using email
|
||||
- ✅ Social logins (Google, Github, Facebook, more coming soon)
|
||||
- ✅ Role-based access management
|
||||
|
||||
## Project Status
|
||||
|
||||
⚠️ **Authorizer is still an early beta! missing features and bugs are to be expected!** If you can stomach it, then bring authentication and authorization to your site today!
|
||||
- ✅ Password-less login with email and magic link
|
||||
|
||||
## Roadmap
|
||||
|
||||
- Password-less login with email and magic link
|
||||
- Support more JWT encryption algorithms (Currently supporting HS256)
|
||||
- 2 Factor authentication
|
||||
- Back office (Admin dashboard to manage user)
|
||||
@@ -70,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 binaries](#install-using-binaries)
|
||||
- [Install instance on heroku](#install-instance-on-Heroku)
|
||||
- [Install instance on railway.app](#install-instance-on-railway)
|
||||
|
||||
## Install using source code
|
||||
|
||||
@@ -95,13 +92,12 @@ binaries are baked with required deployment files and bundled. You can download
|
||||
|
||||
- Mac OSX
|
||||
- Linux
|
||||
- Windows
|
||||
|
||||
### Step 1: Download and unzip bundle
|
||||
|
||||
- Download the Bundle for the specific OS from the [release page](https://github.com/authorizerdev/authorizer/releases)
|
||||
|
||||
> Note: For windows, it includes `.zip` file. For Linux & MacOS, it includes `.tar.gz` file.
|
||||
> Note: For windows, we recommend running using docker image to run authorizer.
|
||||
|
||||
- Unzip using following command
|
||||
|
||||
@@ -111,12 +107,6 @@ binaries are baked with required deployment files and bundled. You can download
|
||||
tar -zxf AUTHORIZER_VERSION -c authorizer
|
||||
```
|
||||
|
||||
- Windows
|
||||
|
||||
```sh
|
||||
unzip AUTHORIZER_VERSION
|
||||
```
|
||||
|
||||
- Change directory to `authorizer`
|
||||
|
||||
```sh
|
||||
@@ -137,12 +127,6 @@ Required environment variables are pre-configured in `.env` file. But based on t
|
||||
./build/server
|
||||
```
|
||||
|
||||
- For windows
|
||||
|
||||
```sh
|
||||
./build/server.exe
|
||||
```
|
||||
|
||||
> Note: For mac users, you might have to give binary the permission to execute. Here is the command you can use to grant permission `xattr -d com.apple.quarantine build/server`
|
||||
|
||||
## Install instance on Heroku
|
||||
@@ -151,12 +135,23 @@ Deploy Authorizer using [heroku](https://github.com/authorizerdev/authorizer-her
|
||||
<br/><br/>
|
||||
[](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
|
||||
|
||||
- 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.
|
||||
> 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
|
||||
|
||||
@@ -197,3 +192,9 @@ This example demonstrates how you can use [`@authorizerdev/authorizer-js`](/auth
|
||||
onLoad();
|
||||
</script>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Support my work
|
||||
|
||||
<a href="https://www.buymeacoffee.com/lakhansamani" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
|
||||
|
10
TODO.md
10
TODO.md
@@ -1,5 +1,15 @@
|
||||
# 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
|
||||
|
||||
- Multiple sessions for users to login use hMset from redis for this
|
||||
|
@@ -1,3 +1,14 @@
|
||||
# Authorizer APP
|
||||
|
||||
App that can be used as login wall for your any application in combination with @authorizerdev/@authorizer.js
|
||||
App that can be used as login wall for your any application in combination with @authorizerdev/@authorizer.js
|
||||
|
||||
### Getting started
|
||||
|
||||
**Setting up locally**
|
||||
|
||||
- `cd app`
|
||||
- `npm start`
|
||||
|
||||
**Creating production build**
|
||||
|
||||
- `make build-app`
|
||||
|
@@ -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__,
|
||||
});
|
1006
app/package-lock.json
generated
1006
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -4,18 +4,20 @@
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"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": [],
|
||||
"author": "Lakhan Samani",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-react": "^0.1.0-beta.19",
|
||||
"@authorizerdev/authorizer-react": "latest",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"esbuild": "^0.12.17",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-is": "^17.0.2",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"typescript": "^4.3.5"
|
||||
},
|
||||
|
@@ -42,7 +42,7 @@ export default function App() {
|
||||
<BrowserRouter>
|
||||
<AuthorizerProvider
|
||||
config={{
|
||||
authorizerURL: globalState.authorizerURL,
|
||||
authorizerURL: window.location.origin,
|
||||
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 { useAuthorizer } from '@authorizerdev/authorizer-react';
|
||||
import Dashboard from './pages/dashboard';
|
||||
import Login from './pages/login';
|
||||
import ResetPassword from './pages/rest-password';
|
||||
|
||||
const ResetPassword = lazy(() => import('./pages/rest-password'));
|
||||
const Login = lazy(() => import('./pages/login'));
|
||||
const Dashboard = lazy(() => import('./pages/dashboard'));
|
||||
|
||||
export default function Root() {
|
||||
const { token, loading, config } = useAuthorizer();
|
||||
@@ -24,22 +25,26 @@ export default function Root() {
|
||||
|
||||
if (token) {
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/app" exact>
|
||||
<Dashboard />
|
||||
</Route>
|
||||
</Switch>
|
||||
<Suspense fallback={<></>}>
|
||||
<Switch>
|
||||
<Route path="/app" exact>
|
||||
<Dashboard />
|
||||
</Route>
|
||||
</Switch>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path="/app" exact>
|
||||
<Login />
|
||||
</Route>
|
||||
<Route path="/app/reset-password">
|
||||
<ResetPassword />
|
||||
</Route>
|
||||
</Switch>
|
||||
<Suspense fallback={<></>}>
|
||||
<Switch>
|
||||
<Route path="/app" exact>
|
||||
<Login />
|
||||
</Route>
|
||||
<Route path="/app/reset-password">
|
||||
<ResetPassword />
|
||||
</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 ReactDOM from 'react-dom';
|
||||
import App from './App';
|
||||
import './index.css';
|
||||
|
||||
ReactDOM.render(<App />, document.getElementById('root'));
|
||||
|
@@ -2,11 +2,11 @@ import React, { Fragment } from 'react';
|
||||
import { AuthorizerResetPassword } from '@authorizerdev/authorizer-react';
|
||||
|
||||
export default function ResetPassword() {
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 style={{ textAlign: 'center' }}>Reset Password</h1>
|
||||
<br />
|
||||
<AuthorizerResetPassword />
|
||||
</Fragment>
|
||||
);
|
||||
return (
|
||||
<Fragment>
|
||||
<h1 style={{ textAlign: 'center' }}>Reset Password</h1>
|
||||
<br />
|
||||
<AuthorizerResetPassword />
|
||||
</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"
|
||||
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
|
||||
tar cvfz ${FILE_NAME} .env app/build build templates
|
||||
tar cvfz ${FILE_NAME} .env app/build build templates dashboard/build
|
||||
AUTH="Authorization: token $GITHUB_TOKEN"
|
||||
RELASE_INFO=$(curl -sH "$AUTH" https://api.github.com/repos/authorizerdev/authorizer/releases/tags/${VERSION})
|
||||
echo $RELASE_INFO
|
||||
|
@@ -1,44 +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 = ""
|
||||
PORT = "8080"
|
||||
REDIS_URL = ""
|
||||
IS_PROD = false
|
||||
COOKIE_NAME = ""
|
||||
RESET_PASSWORD_URL = ""
|
||||
DISABLE_EMAIL_VERIFICATION = "false"
|
||||
DISABLE_BASIC_AUTHENTICATION = "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
|
||||
|
||||
var (
|
||||
const (
|
||||
// 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"
|
||||
// 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="
|
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"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/enum"
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/driver/mysql"
|
||||
"gorm.io/driver/postgres"
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/schema"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers/arangodb"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers/mongodb"
|
||||
"github.com/authorizerdev/authorizer/server/db/providers/sql"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
)
|
||||
|
||||
type Manager interface {
|
||||
SaveUser(user User) (User, error)
|
||||
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
|
||||
// Provider returns the current database provider
|
||||
var Provider providers.Provider
|
||||
|
||||
func InitDB() {
|
||||
var db *gorm.DB
|
||||
var err error
|
||||
ormConfig := &gorm.Config{
|
||||
NamingStrategy: schema.NamingStrategy{
|
||||
TablePrefix: "authorizer_",
|
||||
},
|
||||
}
|
||||
if constants.DATABASE_TYPE == enum.Postgres.String() {
|
||||
db, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig)
|
||||
}
|
||||
if constants.DATABASE_TYPE == enum.Mysql.String() {
|
||||
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)
|
||||
|
||||
isSQL := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeArangodb && envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) != constants.DbTypeMongodb
|
||||
isArangoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeArangodb
|
||||
isMongoDB := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyDatabaseType) == constants.DbTypeMongodb
|
||||
|
||||
if isSQL {
|
||||
Provider, err = sql.NewProvider()
|
||||
if err != nil {
|
||||
log.Fatal("=> error setting sql provider:", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.Fatal("Failed to init db:", err)
|
||||
} else {
|
||||
db.AutoMigrate(&User{}, &VerificationRequest{}, &Role{}, &Session{})
|
||||
if isArangoDB {
|
||||
Provider, err = arangodb.NewProvider()
|
||||
if err != nil {
|
||||
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 (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"crypto/tls"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"mime/quotedprintable"
|
||||
"net/smtp"
|
||||
"strings"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
gomail "gopkg.in/mail.v2"
|
||||
)
|
||||
|
||||
/**
|
||||
Using: https://github.com/tangingw/go_smtp/blob/master/send_mail.go
|
||||
For gmail add instruction to enable less security
|
||||
// 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))
|
||||
// addEmailTemplate is used to add html template in email body
|
||||
func addEmailTemplate(a string, b map[string]interface{}, templateName string) string {
|
||||
tmpl, err := template.New(templateName).Parse(a)
|
||||
if err != nil {
|
||||
output, _ := json.Marshal(b)
|
||||
return string(output)
|
||||
}
|
||||
buf := &bytes.Buffer{}
|
||||
err = tmpl.Execute(buf, b)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s := buf.String()
|
||||
return s
|
||||
}
|
||||
|
||||
// SendMail function to send mail
|
||||
func SendMail(to []string, Subject, bodyMessage string) error {
|
||||
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)
|
||||
return err
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
190
server/env.go
190
server/env.go
@@ -1,190 +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.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_EMAIL_VERIFICATION == "" && constants.DISABLE_BASIC_AUTHENTICATION == "false" {
|
||||
if constants.SMTP_HOST == "" || constants.SENDER_EMAIL == "" || constants.SENDER_PASSWORD == "" {
|
||||
constants.DISABLE_EMAIL_VERIFICATION = "true"
|
||||
} else {
|
||||
constants.DISABLE_EMAIL_VERIFICATION = "false"
|
||||
}
|
||||
}
|
||||
|
||||
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,30 +3,38 @@ module github.com/authorizerdev/authorizer/server
|
||||
go 1.16
|
||||
|
||||
require (
|
||||
github.com/99designs/gqlgen v0.13.0
|
||||
github.com/gin-contrib/location v0.0.2 // indirect
|
||||
github.com/99designs/gqlgen v0.14.0
|
||||
github.com/arangodb/go-driver v1.2.1
|
||||
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/go-playground/validator/v10 v10.8.0 // indirect
|
||||
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/google/uuid v1.3.0
|
||||
github.com/jackc/pgproto3/v2 v2.1.0 // indirect
|
||||
github.com/joho/godotenv v1.3.0
|
||||
github.com/json-iterator/go v1.1.11 // 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/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.1 // indirect
|
||||
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
|
||||
github.com/stretchr/testify v1.7.0
|
||||
github.com/ugorji/go v1.2.6 // indirect
|
||||
github.com/vektah/gqlparser/v2 v2.1.0
|
||||
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
|
||||
github.com/vektah/gqlparser/v2 v2.2.0
|
||||
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/oauth2 v0.0.0-20210628180205-a41e5a781914
|
||||
google.golang.org/appengine v1.6.7 // indirect
|
||||
google.golang.org/protobuf v1.27.1 // indirect
|
||||
gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect
|
||||
gopkg.in/mail.v2 v2.3.1
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gorm.io/driver/mysql v1.1.1
|
||||
gorm.io/driver/postgres v1.1.0
|
||||
gorm.io/driver/sqlite v1.1.4
|
||||
gorm.io/gorm v1.21.11
|
||||
rogchap.com/v8go v0.6.0 // indirect
|
||||
gorm.io/driver/mysql v1.2.1
|
||||
gorm.io/driver/postgres v1.2.3
|
||||
gorm.io/driver/sqlite v1.2.6
|
||||
gorm.io/driver/sqlserver v1.2.1
|
||||
gorm.io/gorm v1.22.4
|
||||
)
|
||||
|
584
server/go.sum
584
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
|
||||
|
||||
type AdminUpdateUserInput struct {
|
||||
ID string `json:"id"`
|
||||
Email *string `json:"email"`
|
||||
FirstName *string `json:"firstName"`
|
||||
LastName *string `json:"lastName"`
|
||||
Image *string `json:"image"`
|
||||
Roles []*string `json:"roles"`
|
||||
type AdminLoginInput struct {
|
||||
AdminSecret string `json:"admin_secret"`
|
||||
}
|
||||
|
||||
type AdminSignupInput struct {
|
||||
AdminSecret string `json:"admin_secret"`
|
||||
}
|
||||
|
||||
type AuthResponse struct {
|
||||
Message string `json:"message"`
|
||||
AccessToken *string `json:"accessToken"`
|
||||
AccessTokenExpiresAt *int64 `json:"accessTokenExpiresAt"`
|
||||
User *User `json:"user"`
|
||||
Message string `json:"message"`
|
||||
AccessToken *string `json:"access_token"`
|
||||
ExpiresAt *int64 `json:"expires_at"`
|
||||
User *User `json:"user"`
|
||||
}
|
||||
|
||||
type DeleteUserInput struct {
|
||||
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 {
|
||||
Message string `json:"message"`
|
||||
Reason string `json:"reason"`
|
||||
@@ -37,24 +72,30 @@ type LoginInput struct {
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
type MagicLinkLoginInput struct {
|
||||
Email string `json:"email"`
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
type Meta struct {
|
||||
Version string `json:"version"`
|
||||
IsGoogleLoginEnabled bool `json:"isGoogleLoginEnabled"`
|
||||
IsFacebookLoginEnabled bool `json:"isFacebookLoginEnabled"`
|
||||
IsTwitterLoginEnabled bool `json:"isTwitterLoginEnabled"`
|
||||
IsGithubLoginEnabled bool `json:"isGithubLoginEnabled"`
|
||||
IsEmailVerificationEnabled bool `json:"isEmailVerificationEnabled"`
|
||||
IsBasicAuthenticationEnabled bool `json:"isBasicAuthenticationEnabled"`
|
||||
IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
|
||||
IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
|
||||
IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
|
||||
IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
|
||||
IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
|
||||
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
|
||||
}
|
||||
|
||||
type ResendVerifyEmailInput struct {
|
||||
Email string `json:"email"`
|
||||
Email string `json:"email"`
|
||||
Identifier string `json:"identifier"`
|
||||
}
|
||||
|
||||
type ResetPasswordInput struct {
|
||||
Token string `json:"token"`
|
||||
Password string `json:"password"`
|
||||
ConfirmPassword string `json:"confirmPassword"`
|
||||
ConfirmPassword string `json:"confirm_password"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
@@ -62,36 +103,104 @@ type Response struct {
|
||||
}
|
||||
|
||||
type SignUpInput struct {
|
||||
FirstName *string `json:"firstName"`
|
||||
LastName *string `json:"lastName"`
|
||||
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"`
|
||||
ConfirmPassword string `json:"confirmPassword"`
|
||||
Image *string `json:"image"`
|
||||
ConfirmPassword string `json:"confirm_password"`
|
||||
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 {
|
||||
OldPassword *string `json:"oldPassword"`
|
||||
NewPassword *string `json:"newPassword"`
|
||||
ConfirmNewPassword *string `json:"confirmNewPassword"`
|
||||
FirstName *string `json:"firstName"`
|
||||
LastName *string `json:"lastName"`
|
||||
Image *string `json:"image"`
|
||||
OldPassword *string `json:"old_password"`
|
||||
NewPassword *string `json:"new_password"`
|
||||
ConfirmNewPassword *string `json:"confirm_new_password"`
|
||||
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 {
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
SignupMethod string `json:"signupMethod"`
|
||||
FirstName *string `json:"firstName"`
|
||||
LastName *string `json:"lastName"`
|
||||
EmailVerifiedAt *int64 `json:"emailVerifiedAt"`
|
||||
Image *string `json:"image"`
|
||||
CreatedAt *int64 `json:"createdAt"`
|
||||
UpdatedAt *int64 `json:"updatedAt"`
|
||||
Roles []string `json:"roles"`
|
||||
ID string `json:"id"`
|
||||
Email string `json:"email"`
|
||||
EmailVerified bool `json:"email_verified"`
|
||||
SignupMethods string `json:"signup_methods"`
|
||||
GivenName *string `json:"given_name"`
|
||||
FamilyName *string `json:"family_name"`
|
||||
MiddleName *string `json:"middle_name"`
|
||||
Nickname *string `json:"nickname"`
|
||||
PreferredUsername *string `json:"preferred_username"`
|
||||
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 {
|
||||
@@ -100,8 +209,8 @@ type VerificationRequest struct {
|
||||
Token *string `json:"token"`
|
||||
Email *string `json:"email"`
|
||||
Expires *int64 `json:"expires"`
|
||||
CreatedAt *int64 `json:"createdAt"`
|
||||
UpdatedAt *int64 `json:"updatedAt"`
|
||||
CreatedAt *int64 `json:"created_at"`
|
||||
UpdatedAt *int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
type VerifyEmailInput struct {
|
||||
|
@@ -7,25 +7,33 @@ scalar Any
|
||||
|
||||
type Meta {
|
||||
version: String!
|
||||
isGoogleLoginEnabled: Boolean!
|
||||
isFacebookLoginEnabled: Boolean!
|
||||
isTwitterLoginEnabled: Boolean!
|
||||
isGithubLoginEnabled: Boolean!
|
||||
isEmailVerificationEnabled: Boolean!
|
||||
isBasicAuthenticationEnabled: Boolean!
|
||||
is_google_login_enabled: Boolean!
|
||||
is_facebook_login_enabled: Boolean!
|
||||
is_github_login_enabled: Boolean!
|
||||
is_email_verification_enabled: Boolean!
|
||||
is_basic_authentication_enabled: Boolean!
|
||||
is_magic_link_login_enabled: Boolean!
|
||||
}
|
||||
|
||||
type User {
|
||||
id: ID!
|
||||
email: String!
|
||||
signupMethod: String!
|
||||
firstName: String
|
||||
lastName: String
|
||||
emailVerifiedAt: Int64
|
||||
image: String
|
||||
createdAt: Int64
|
||||
updatedAt: Int64
|
||||
email_verified: Boolean!
|
||||
signup_methods: String!
|
||||
given_name: String
|
||||
family_name: String
|
||||
middle_name: String
|
||||
nickname: String
|
||||
# defaults to email
|
||||
preferred_username: String
|
||||
gender: String
|
||||
birthdate: String
|
||||
phone_number: String
|
||||
phone_number_verified: Boolean
|
||||
picture: String
|
||||
roles: [String!]!
|
||||
created_at: Int64
|
||||
updated_at: Int64
|
||||
}
|
||||
|
||||
type VerificationRequest {
|
||||
@@ -34,8 +42,8 @@ type VerificationRequest {
|
||||
token: String
|
||||
email: String
|
||||
expires: Int64
|
||||
createdAt: Int64
|
||||
updatedAt: Int64
|
||||
created_at: Int64
|
||||
updated_at: Int64
|
||||
}
|
||||
|
||||
type Error {
|
||||
@@ -45,8 +53,8 @@ type Error {
|
||||
|
||||
type AuthResponse {
|
||||
message: String!
|
||||
accessToken: String
|
||||
accessTokenExpiresAt: Int64
|
||||
access_token: String
|
||||
expires_at: Int64
|
||||
user: User
|
||||
}
|
||||
|
||||
@@ -54,13 +62,98 @@ type Response {
|
||||
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 {
|
||||
firstName: String
|
||||
lastName: 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!
|
||||
confirmPassword: String!
|
||||
image: String
|
||||
confirm_password: String!
|
||||
roles: [String!]
|
||||
}
|
||||
|
||||
@@ -76,25 +169,36 @@ input VerifyEmailInput {
|
||||
|
||||
input ResendVerifyEmailInput {
|
||||
email: String!
|
||||
identifier: String!
|
||||
}
|
||||
|
||||
input UpdateProfileInput {
|
||||
oldPassword: String
|
||||
newPassword: String
|
||||
confirmNewPassword: String
|
||||
firstName: String
|
||||
lastName: String
|
||||
image: String
|
||||
old_password: String
|
||||
new_password: String
|
||||
confirm_new_password: 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!
|
||||
email: String
|
||||
firstName: String
|
||||
lastName: String
|
||||
image: String
|
||||
email_verified: Boolean
|
||||
given_name: String
|
||||
family_name: String
|
||||
middle_name: String
|
||||
nickname: String
|
||||
gender: String
|
||||
birthdate: String
|
||||
phone_number: String
|
||||
picture: String
|
||||
roles: [String]
|
||||
}
|
||||
|
||||
@@ -105,30 +209,44 @@ input ForgotPasswordInput {
|
||||
input ResetPasswordInput {
|
||||
token: String!
|
||||
password: String!
|
||||
confirmPassword: String!
|
||||
confirm_password: String!
|
||||
}
|
||||
|
||||
input DeleteUserInput {
|
||||
email: String!
|
||||
}
|
||||
|
||||
input MagicLinkLoginInput {
|
||||
email: String!
|
||||
roles: [String!]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
signup(params: SignUpInput!): AuthResponse!
|
||||
login(params: LoginInput!): AuthResponse!
|
||||
magic_link_login(params: MagicLinkLoginInput!): Response!
|
||||
logout: Response!
|
||||
updateProfile(params: UpdateProfileInput!): Response!
|
||||
adminUpdateUser(params: AdminUpdateUserInput!): User!
|
||||
verifyEmail(params: VerifyEmailInput!): AuthResponse!
|
||||
resendVerifyEmail(params: ResendVerifyEmailInput!): Response!
|
||||
forgotPassword(params: ForgotPasswordInput!): Response!
|
||||
resetPassword(params: ResetPasswordInput!): Response!
|
||||
deleteUser(params: DeleteUserInput!): Response!
|
||||
update_profile(params: UpdateProfileInput!): Response!
|
||||
verify_email(params: VerifyEmailInput!): AuthResponse!
|
||||
resend_verify_email(params: ResendVerifyEmailInput!): Response!
|
||||
forgot_password(params: ForgotPasswordInput!): Response!
|
||||
reset_password(params: ResetPasswordInput!): Response!
|
||||
# admin only apis
|
||||
_delete_user(params: DeleteUserInput!): Response!
|
||||
_update_user(params: UpdateUserInput!): User!
|
||||
_admin_signup(params: AdminSignupInput!): Response!
|
||||
_admin_login(params: AdminLoginInput!): Response!
|
||||
_admin_logout: Response!
|
||||
_update_env(params: UpdateEnvInput!): Response!
|
||||
}
|
||||
|
||||
type Query {
|
||||
meta: Meta!
|
||||
users: [User!]!
|
||||
token(roles: [String!]): AuthResponse
|
||||
session(roles: [String!]): AuthResponse!
|
||||
profile: User!
|
||||
verificationRequests: [VerificationRequest!]!
|
||||
# admin only apis
|
||||
_users: [User!]!
|
||||
_verification_requests: [VerificationRequest!]!
|
||||
_admin_session: Response!
|
||||
_env: Env!
|
||||
}
|
||||
|
@@ -12,63 +12,91 @@ import (
|
||||
)
|
||||
|
||||
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) {
|
||||
return resolvers.Login(ctx, params)
|
||||
return resolvers.LoginResolver(ctx, params)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
|
||||
return resolvers.MagicLinkLoginResolver(ctx, params)
|
||||
}
|
||||
|
||||
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) {
|
||||
return resolvers.UpdateProfile(ctx, params)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error) {
|
||||
return resolvers.AdminUpdateUser(ctx, params)
|
||||
return resolvers.UpdateProfileResolver(ctx, params)
|
||||
}
|
||||
|
||||
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) {
|
||||
return resolvers.ResendVerifyEmail(ctx, params)
|
||||
return resolvers.ResendVerifyEmailResolver(ctx, params)
|
||||
}
|
||||
|
||||
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) {
|
||||
return resolvers.ResetPassword(ctx, params)
|
||||
return resolvers.ResetPasswordResolver(ctx, params)
|
||||
}
|
||||
|
||||
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) {
|
||||
return resolvers.Meta(ctx)
|
||||
return resolvers.MetaResolver(ctx)
|
||||
}
|
||||
|
||||
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
|
||||
return resolvers.Users(ctx)
|
||||
}
|
||||
|
||||
func (r *queryResolver) Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
|
||||
return resolvers.Token(ctx, roles)
|
||||
func (r *queryResolver) Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
|
||||
return resolvers.SessionResolver(ctx, roles)
|
||||
}
|
||||
|
||||
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) {
|
||||
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.
|
||||
|
@@ -1,22 +1,25 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"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 {
|
||||
AuthorizerURL string `json:"authorizerURL"`
|
||||
RedirectURL string `json:"redirectURL"`
|
||||
}
|
||||
|
||||
// AppHandler is the handler for the /app route
|
||||
func AppHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
state := c.Query("state")
|
||||
@@ -24,23 +27,17 @@ func AppHandler() gin.HandlerFunc {
|
||||
var stateObj State
|
||||
|
||||
if state == "" {
|
||||
// cookie, err := utils.GetAuthToken(c)
|
||||
// if err != nil {
|
||||
// c.JSON(400, gin.H{"error": "invalid state"})
|
||||
// return
|
||||
// }
|
||||
|
||||
stateObj.AuthorizerURL = constants.AUTHORIZER_URL
|
||||
stateObj.RedirectURL = constants.AUTHORIZER_URL + "/app"
|
||||
stateObj.AuthorizerURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)
|
||||
stateObj.RedirectURL = stateObj.AuthorizerURL + "/app"
|
||||
|
||||
} else {
|
||||
decodedState, err := base64.StdEncoding.DecodeString(state)
|
||||
decodedState, err := utils.DecryptB64(state)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
|
||||
return
|
||||
}
|
||||
|
||||
err = json.Unmarshal(decodedState, &stateObj)
|
||||
err = json.Unmarshal([]byte(decodedState), &stateObj)
|
||||
if err != nil {
|
||||
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
|
||||
return
|
||||
@@ -49,7 +46,7 @@ func AppHandler() gin.HandlerFunc {
|
||||
stateObj.RedirectURL = strings.TrimSuffix(stateObj.RedirectURL, "/")
|
||||
|
||||
// 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"})
|
||||
return
|
||||
}
|
||||
@@ -60,7 +57,7 @@ func AppHandler() gin.HandlerFunc {
|
||||
}
|
||||
|
||||
// validate host and domain of authorizer url
|
||||
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.AUTHORIZER_URL {
|
||||
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) {
|
||||
c.JSON(400, gin.H{"error": "invalid host url"})
|
||||
return
|
||||
}
|
||||
@@ -77,8 +74,8 @@ func AppHandler() gin.HandlerFunc {
|
||||
"data": map[string]string{
|
||||
"authorizerURL": stateObj.AuthorizerURL,
|
||||
"redirectURL": stateObj.RedirectURL,
|
||||
"organizationName": constants.ORGANIZATION_NAME,
|
||||
"organizationLogo": constants.ORGANIZATION_LOGO,
|
||||
"organizationName": envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName),
|
||||
"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"
|
||||
)
|
||||
|
||||
// Defining the Graphql handler
|
||||
// GraphqlHandler is the main handler that handels all the graphql requests
|
||||
func GraphqlHandler() gin.HandlerFunc {
|
||||
// NewExecutableSchema and Config are in the generated.go file
|
||||
// Resolver is in the resolver.go file
|
||||
|
@@ -1,75 +0,0 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/enum"
|
||||
"github.com/authorizerdev/authorizer/server/oauth"
|
||||
"github.com/authorizerdev/authorizer/server/session"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// set host in the oauth state that is useful for redirecting
|
||||
|
||||
func OAuthLoginHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// TODO validate redirect URL
|
||||
redirectURL := c.Query("redirectURL")
|
||||
roles := c.Query("roles")
|
||||
|
||||
if redirectURL == "" {
|
||||
c.JSON(400, gin.H{
|
||||
"error": "invalid redirect url",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if roles != "" {
|
||||
// validate role
|
||||
rolesSplit := strings.Split(roles, ",")
|
||||
|
||||
// use protected roles verification for admin login only.
|
||||
// though if not associated with user, it will be rejected from oauth_callback
|
||||
if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), rolesSplit) {
|
||||
c.JSON(400, gin.H{
|
||||
"error": "invalid role",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
roles = strings.Join(constants.DEFAULT_ROLES, ",")
|
||||
}
|
||||
|
||||
uuid := uuid.New()
|
||||
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
|
||||
|
||||
provider := c.Param("oauth_provider")
|
||||
|
||||
switch provider {
|
||||
case enum.Google.String():
|
||||
session.SetSocailLoginState(oauthStateString, enum.Google.String())
|
||||
// during the init of OAuthProvider authorizer url might be empty
|
||||
oauth.OAuthProvider.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google"
|
||||
url := oauth.OAuthProvider.GoogleConfig.AuthCodeURL(oauthStateString)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
case enum.Github.String():
|
||||
session.SetSocailLoginState(oauthStateString, enum.Github.String())
|
||||
oauth.OAuthProvider.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github"
|
||||
url := oauth.OAuthProvider.GithubConfig.AuthCodeURL(oauthStateString)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
case enum.Facebook.String():
|
||||
session.SetSocailLoginState(oauthStateString, enum.Facebook.String())
|
||||
oauth.OAuthProvider.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook"
|
||||
url := oauth.OAuthProvider.FacebookConfig.AuthCodeURL(oauthStateString)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
default:
|
||||
c.JSON(422, gin.H{
|
||||
"message": "Invalid oauth provider",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
@@ -11,49 +12,173 @@ import (
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"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/session"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/coreos/go-oidc/v3/oidc"
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/oauth2"
|
||||
)
|
||||
|
||||
func processGoogleUserInfo(code string) (db.User, error) {
|
||||
user := db.User{}
|
||||
token, err := oauth.OAuthProvider.GoogleConfig.Exchange(oauth2.NoContext, code)
|
||||
// OAuthCallbackHandler handles the OAuth callback for various oauth providers
|
||||
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()
|
||||
oauth2Token, err := oauth.OAuthProviders.GoogleConfig.Exchange(ctx, code)
|
||||
if err != nil {
|
||||
return user, fmt.Errorf("invalid google exchange code: %s", err.Error())
|
||||
}
|
||||
client := oauth.OAuthProvider.GoogleConfig.Client(oauth2.NoContext, token)
|
||||
response, err := client.Get(constants.GoogleUserInfoURL)
|
||||
if err != nil {
|
||||
return user, err
|
||||
|
||||
verifier := oauth.OIDCProviders.GoogleOIDC.Verifier(&oidc.Config{ClientID: oauth.OAuthProviders.GoogleConfig.ClientID})
|
||||
|
||||
// Extract the ID Token from OAuth2 token.
|
||||
rawIDToken, ok := oauth2Token.Extra("id_token").(string)
|
||||
if !ok {
|
||||
return user, fmt.Errorf("unable to extract id_token")
|
||||
}
|
||||
|
||||
defer response.Body.Close()
|
||||
body, err := ioutil.ReadAll(response.Body)
|
||||
// Parse and verify ID Token payload.
|
||||
idToken, err := verifier.Verify(ctx, rawIDToken)
|
||||
if err != nil {
|
||||
return user, fmt.Errorf("failed to read google response body: %s", err.Error())
|
||||
return user, fmt.Errorf("unable to verify id_token: %s", err.Error())
|
||||
}
|
||||
|
||||
userRawData := make(map[string]string)
|
||||
json.Unmarshal(body, &userRawData)
|
||||
|
||||
user = db.User{
|
||||
FirstName: userRawData["given_name"],
|
||||
LastName: userRawData["family_name"],
|
||||
Image: userRawData["picture"],
|
||||
Email: userRawData["email"],
|
||||
EmailVerifiedAt: time.Now().Unix(),
|
||||
if err := idToken.Claims(&user); err != nil {
|
||||
return user, fmt.Errorf("unable to extract claims")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func processGithubUserInfo(code string) (db.User, error) {
|
||||
user := db.User{}
|
||||
token, err := oauth.OAuthProvider.GithubConfig.Exchange(oauth2.NoContext, code)
|
||||
func processGithubUserInfo(code string) (models.User, error) {
|
||||
user := models.User{}
|
||||
token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
|
||||
if err != nil {
|
||||
return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
|
||||
}
|
||||
@@ -89,20 +214,22 @@ func processGithubUserInfo(code string) (db.User, error) {
|
||||
if len(name) > 1 && strings.TrimSpace(name[1]) != "" {
|
||||
lastName = name[0]
|
||||
}
|
||||
user = db.User{
|
||||
FirstName: firstName,
|
||||
LastName: lastName,
|
||||
Image: userRawData["avatar_url"],
|
||||
Email: userRawData["email"],
|
||||
EmailVerifiedAt: time.Now().Unix(),
|
||||
|
||||
picture := userRawData["avatar_url"]
|
||||
|
||||
user = models.User{
|
||||
GivenName: &firstName,
|
||||
FamilyName: &lastName,
|
||||
Picture: &picture,
|
||||
Email: userRawData["email"],
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func processFacebookUserInfo(code string) (db.User, error) {
|
||||
user := db.User{}
|
||||
token, err := oauth.OAuthProvider.FacebookConfig.Exchange(oauth2.NoContext, code)
|
||||
func processFacebookUserInfo(code string) (models.User, error) {
|
||||
user := models.User{}
|
||||
token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
|
||||
if err != nil {
|
||||
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
|
||||
}
|
||||
@@ -114,7 +241,7 @@ func processFacebookUserInfo(code string) (db.User, error) {
|
||||
|
||||
response, err := client.Do(req)
|
||||
if err != nil {
|
||||
log.Println("err:", err)
|
||||
log.Println("error processing facebook user info:", err)
|
||||
return user, err
|
||||
}
|
||||
|
||||
@@ -131,140 +258,16 @@ func processFacebookUserInfo(code string) (db.User, error) {
|
||||
|
||||
picObject := userRawData["picture"].(map[string]interface{})["data"]
|
||||
picDataObject := picObject.(map[string]interface{})
|
||||
user = db.User{
|
||||
FirstName: fmt.Sprintf("%v", userRawData["first_name"]),
|
||||
LastName: fmt.Sprintf("%v", userRawData["last_name"]),
|
||||
Image: fmt.Sprintf("%v", picDataObject["url"]),
|
||||
Email: email,
|
||||
EmailVerifiedAt: time.Now().Unix(),
|
||||
firstName := fmt.Sprintf("%v", userRawData["first_name"])
|
||||
lastName := fmt.Sprintf("%v", userRawData["last_name"])
|
||||
picture := fmt.Sprintf("%v", picDataObject["url"])
|
||||
|
||||
user = models.User{
|
||||
GivenName: &firstName,
|
||||
FamilyName: &lastName,
|
||||
Picture: &picture,
|
||||
Email: email,
|
||||
}
|
||||
|
||||
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 + "," + enum.Github.String()
|
||||
}
|
||||
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)
|
||||
}
|
||||
}
|
92
server/handlers/oauth_login.go
Normal file
92
server/handlers/oauth_login.go
Normal file
@@ -0,0 +1,92 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
"github.com/authorizerdev/authorizer/server/oauth"
|
||||
"github.com/authorizerdev/authorizer/server/session"
|
||||
"github.com/authorizerdev/authorizer/server/utils"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/google/uuid"
|
||||
)
|
||||
|
||||
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
|
||||
func OAuthLoginHandler() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
// TODO validate redirect URL
|
||||
redirectURL := c.Query("redirectURL")
|
||||
roles := c.Query("roles")
|
||||
|
||||
if redirectURL == "" {
|
||||
c.JSON(400, gin.H{
|
||||
"error": "invalid redirect url",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if roles != "" {
|
||||
// validate role
|
||||
rolesSplit := strings.Split(roles, ",")
|
||||
|
||||
// use protected roles verification for admin login only.
|
||||
// though if not associated with user, it will be rejected from oauth_callback
|
||||
if !utils.IsValidRoles(append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), rolesSplit) {
|
||||
c.JSON(400, gin.H{
|
||||
"error": "invalid role",
|
||||
})
|
||||
return
|
||||
}
|
||||
} else {
|
||||
roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
||||
}
|
||||
|
||||
uuid := uuid.New()
|
||||
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
|
||||
|
||||
provider := c.Param("oauth_provider")
|
||||
isProviderConfigured := true
|
||||
switch provider {
|
||||
case constants.SignupMethodGoogle:
|
||||
if oauth.OAuthProviders.GoogleConfig == nil {
|
||||
isProviderConfigured = false
|
||||
break
|
||||
}
|
||||
session.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle)
|
||||
// during the init of OAuthProvider authorizer url might be empty
|
||||
oauth.OAuthProviders.GoogleConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/google"
|
||||
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
case constants.SignupMethodGithub:
|
||||
if oauth.OAuthProviders.GithubConfig == nil {
|
||||
isProviderConfigured = false
|
||||
break
|
||||
}
|
||||
session.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub)
|
||||
oauth.OAuthProviders.GithubConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/github"
|
||||
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
case constants.SignupMethodFacebook:
|
||||
if oauth.OAuthProviders.FacebookConfig == nil {
|
||||
isProviderConfigured = false
|
||||
break
|
||||
}
|
||||
session.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook)
|
||||
oauth.OAuthProviders.FacebookConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/facebook"
|
||||
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
|
||||
c.Redirect(http.StatusTemporaryRedirect, url)
|
||||
default:
|
||||
c.JSON(422, gin.H{
|
||||
"message": "Invalid oauth provider",
|
||||
})
|
||||
}
|
||||
|
||||
if !isProviderConfigured {
|
||||
c.JSON(422, gin.H{
|
||||
"message": provider + " not configured",
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user