Compare commits

...

9 Commits

Author SHA1 Message Date
Lakhan Samani
6ca37a0d50 feat: add oidc for google login 2021-12-03 22:55:27 +05:30
Lakhan Samani
a9cf301344 feat: update app dependencies 2021-11-15 04:12:28 +05:30
Lakhan Samani
0305a719db feat: add support for magic link login (#65)
* feat: add support for magic link login

* update readme
2021-11-12 05:22:03 +05:30
Lakhan Samani
4269e2242c fix: sample script 2021-10-31 11:06:07 +05:30
Lakhan Samani
71e4e35de6 add sample for custom access token script 2021-10-31 11:05:06 +05:30
Lakhan Samani
3aeb3b8d67 fix: remove log 2021-10-30 16:46:37 +05:30
Lakhan Samani
e1951bfbe0 fix: restore release 2021-10-29 21:47:28 +05:30
Lakhan Samani
1299ec5f9c fix: enable win release 2021-10-29 21:43:38 +05:30
Lakhan Samani
bc53974d2a fix: use otto instead of v8go 2021-10-29 21:41:47 +05:30
25 changed files with 600 additions and 1016 deletions

View File

@@ -8,4 +8,5 @@ JWT_TYPE=HS256
ROLES=user ROLES=user
DEFAULT_ROLES=user DEFAULT_ROLES=user
PROTECTED_ROLES=admin 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;}

View File

@@ -5,26 +5,29 @@ on:
jobs: jobs:
releases: releases:
name: Release Authorizer Binary name: Release Authorizer Binary
strategy: runs-on: ubuntu-latest
matrix:
go-version: [1.16.4]
platform: [ubuntu-18.04]
runs-on: ${{ matrix.platform }}
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Install Go - name: Install dependencies
uses: actions/setup-go@v2 run: |
with: sudo apt-get install build-essential wget zip gcc-mingw-w64 && \
go-version: ${{ matrix.go-version }} sudo apt-get remove --auto-remove golang-go && \
# - name: Install dependencies sudo rm -rf /usr/bin/go &&\
# run: | wget --progress=dot:mega https://golang.org/dl/go1.17.1.linux-amd64.tar.gz -O go-linux.tar.gz && \
# sudo apt-get install build-essential wget zip && \ sudo tar -zxf go-linux.tar.gz && \
# go version && \ sudo mv go /usr/bin/ && \
# 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 && \ sudo mkdir -p /go/bin /go/src /go/pkg && \
# tar -zxf github-assets-uploader.tar.gz && \ export GO_HOME=/usr/bin/go && \
# sudo mv github-assets-uploader /usr/sbin/ && \ export GOPATH=/go && \
# sudo rm -f github-assets-uploader.tar.gz && \ export PATH=${GOPATH}/bin:${GO_HOME}/bin/:$PATH && \
# github-assets-uploader -version echo "/usr/bin/go/bin" >> $GITHUB_PATH
echo "/usr/bin/x86_64-w64-mingw32-gcc" >> GITHUB_PATH
go version && \
wget --no-check-certificate --progress=dot:mega https://github.com/wangyoucao577/assets-uploader/releases/download/v0.3.0/github-assets-uploader-v0.3.0-linux-amd64.tar.gz -O github-assets-uploader.tar.gz && \
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
- name: Print Go paths - name: Print Go paths
run: whereis go run: whereis go
- name: Print Go Version - name: Print Go Version
@@ -33,26 +36,21 @@ jobs:
run: echo VERSION=$(basename ${GITHUB_REF}) >> ${GITHUB_ENV} run: echo VERSION=$(basename ${GITHUB_REF}) >> ${GITHUB_ENV}
- name: Copy .env file - name: Copy .env file
run: mv .env.sample .env run: mv .env.sample .env
# - name: Package files for windows - name: Package files for windows
# run: | run: |
# make clean && \ make clean && \
# CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \ CGO_ENABLED=1 GOOS=windows CC=/usr/bin/x86_64-w64-mingw32-gcc make && \
# mv build/server build/server.exe && \ mv build/server build/server.exe && \
# zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates zip -vr authorizer-${VERSION}-windows-amd64.zip .env app/build build templates
- name: Package files for linux - name: Package files for linux
run: | run: |
make clean && \ make clean && \
CGO_ENABLED=1 make && \ CGO_ENABLED=1 make && \
tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates tar cvfz authorizer-${VERSION}-linux-amd64.tar.gz .env app/build build templates
- name: Upload asset - name: Upload assets
uses: softprops/action-gh-release@v1 run: |
with: github-assets-uploader -f authorizer-${VERSION}-windows-amd64.zip -mediatype application/zip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION} && \
files: authorizer-${VERSION}-linux-amd64.tar.gz github-assets-uploader -f authorizer-${VERSION}-linux-amd64.tar.gz -mediatype application/gzip -repo authorizerdev/authorizer -token ${{secrets.RELEASE_TOKEN}} -tag ${VERSION}
token: ${{secrets.RELEASE_TOKEN}}
tag_name: ${VERSION}
# - name: Upload assets
# run: |
# 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 - name: Log in to Docker Hub
uses: docker/login-action@v1 uses: docker/login-action@v1
with: with:

View File

@@ -7,7 +7,7 @@ ARG VERSION="latest"
ENV VERSION="$VERSION" ENV VERSION="$VERSION"
RUN echo "$VERSION" RUN echo "$VERSION"
RUN apk add build-base &&\ RUN apk add build-base nodejs &&\
make clean && make && \ make clean && make && \
chmod 777 build/server chmod 777 build/server

View File

@@ -30,14 +30,10 @@
- ✅ Forgot password flow using email - ✅ Forgot password flow using email
- ✅ Social logins (Google, Github, Facebook, more coming soon) - ✅ Social logins (Google, Github, Facebook, more coming soon)
- ✅ Role-based access management - ✅ Role-based access management
- ✅ Password-less login with email and magic link
## 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!
## Roadmap ## Roadmap
- Password-less login with email and magic link
- Support more JWT encryption algorithms (Currently supporting HS256) - Support more JWT encryption algorithms (Currently supporting HS256)
- 2 Factor authentication - 2 Factor authentication
- Back office (Admin dashboard to manage user) - Back office (Admin dashboard to manage user)

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

985
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -10,12 +10,13 @@
"author": "Lakhan Samani", "author": "Lakhan Samani",
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@authorizerdev/authorizer-react": "^0.1.0-beta.19", "@authorizerdev/authorizer-react": "^0.1.0",
"@types/react": "^17.0.15", "@types/react": "^17.0.15",
"@types/react-dom": "^17.0.9", "@types/react-dom": "^17.0.9",
"esbuild": "^0.12.17", "esbuild": "^0.12.17",
"react": "^17.0.2", "react": "^17.0.2",
"react-dom": "^17.0.2", "react-dom": "^17.0.2",
"react-is": "^17.0.2",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"typescript": "^4.3.5" "typescript": "^4.3.5"
}, },

View File

@@ -21,6 +21,7 @@ var (
RESET_PASSWORD_URL = "" RESET_PASSWORD_URL = ""
DISABLE_EMAIL_VERIFICATION = "false" DISABLE_EMAIL_VERIFICATION = "false"
DISABLE_BASIC_AUTHENTICATION = "false" DISABLE_BASIC_AUTHENTICATION = "false"
DISABLE_MAGIC_LOGIN = "false"
// ROLES // ROLES
ROLES = []string{} ROLES = []string{}

View File

@@ -64,6 +64,7 @@ func InitEnv() {
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/") constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION")
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION")
constants.DISABLE_MAGIC_LOGIN = os.Getenv("DISABLE_MAGIC_LOGIN")
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM") constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
if constants.ADMIN_SECRET == "" { if constants.ADMIN_SECRET == "" {
@@ -126,14 +127,18 @@ func InitEnv() {
constants.DISABLE_BASIC_AUTHENTICATION = "false" constants.DISABLE_BASIC_AUTHENTICATION = "false"
} }
if constants.DISABLE_EMAIL_VERIFICATION == "" && constants.DISABLE_BASIC_AUTHENTICATION == "false" { if constants.DISABLE_MAGIC_LOGIN == "" {
if constants.SMTP_HOST == "" || constants.SENDER_EMAIL == "" || constants.SENDER_PASSWORD == "" { constants.DISABLE_MAGIC_LOGIN = "false"
constants.DISABLE_EMAIL_VERIFICATION = "true"
} else {
constants.DISABLE_EMAIL_VERIFICATION = "false"
}
} }
if constants.SMTP_HOST == "" || constants.SENDER_EMAIL == "" || constants.SENDER_PASSWORD == "" {
constants.DISABLE_EMAIL_VERIFICATION = "true"
} else if constants.DISABLE_EMAIL_VERIFICATION == "" {
constants.DISABLE_EMAIL_VERIFICATION = "false"
}
log.Println("=> disable email verification:", constants.DISABLE_EMAIL_VERIFICATION)
rolesSplit := strings.Split(os.Getenv("ROLES"), ",") rolesSplit := strings.Split(os.Getenv("ROLES"), ",")
roles := []string{} roles := []string{}
if len(rolesSplit) == 0 { if len(rolesSplit) == 0 {

View File

@@ -4,6 +4,7 @@ go 1.16
require ( require (
github.com/99designs/gqlgen v0.13.0 github.com/99designs/gqlgen v0.13.0
github.com/coreos/go-oidc/v3 v3.1.0 // indirect
github.com/gin-contrib/location v0.0.2 // indirect github.com/gin-contrib/location v0.0.2 // indirect
github.com/gin-gonic/gin v1.7.2 github.com/gin-gonic/gin v1.7.2
github.com/go-playground/validator/v10 v10.8.0 // indirect github.com/go-playground/validator/v10 v10.8.0 // indirect
@@ -16,6 +17,7 @@ require (
github.com/json-iterator/go v1.1.11 // indirect github.com/json-iterator/go v1.1.11 // indirect
github.com/mattn/go-isatty v0.0.13 // indirect github.com/mattn/go-isatty v0.0.13 // indirect
github.com/mattn/go-sqlite3 v1.14.7 // indirect github.com/mattn/go-sqlite3 v1.14.7 // indirect
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f // indirect
github.com/ugorji/go v1.2.6 // indirect github.com/ugorji/go v1.2.6 // indirect
github.com/vektah/gqlparser/v2 v2.1.0 github.com/vektah/gqlparser/v2 v2.1.0
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97 golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
@@ -28,5 +30,4 @@ require (
gorm.io/driver/postgres v1.1.0 gorm.io/driver/postgres v1.1.0
gorm.io/driver/sqlite v1.1.4 gorm.io/driver/sqlite v1.1.4
gorm.io/gorm v1.21.11 gorm.io/gorm v1.21.11
rogchap.com/v8go v0.6.0 // indirect
) )

View File

@@ -109,6 +109,8 @@ github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8=
github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI=
github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
github.com/coreos/go-oidc/v3 v3.1.0/go.mod h1:rEJ/idjfUyfkBit1eI1fvyr+64/g9dcKpAm8MJMesvo=
github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
@@ -556,6 +558,8 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
github.com/robertkrimen/otto v0.0.0-20211019175142-5b0d97091c6f h1:wOVoULFf7IVSJ9hl8wnQew/kCpchffRb7a81H9/IcS4= github.com/robertkrimen/otto v0.0.0-20211019175142-5b0d97091c6f h1:wOVoULFf7IVSJ9hl8wnQew/kCpchffRb7a81H9/IcS4=
github.com/robertkrimen/otto v0.0.0-20211019175142-5b0d97091c6f/go.mod h1:/mK7FZ3mFYEn9zvNPhpngTyatyehSwte5bJZ4ehL5Xw= github.com/robertkrimen/otto v0.0.0-20211019175142-5b0d97091c6f/go.mod h1:/mK7FZ3mFYEn9zvNPhpngTyatyehSwte5bJZ4ehL5Xw=
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f h1:a7clxaGmmqtdNTXyvrp/lVO/Gnkzlhc/+dLs5v965GM=
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f/go.mod h1:/mK7FZ3mFYEn9zvNPhpngTyatyehSwte5bJZ4ehL5Xw=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
@@ -735,6 +739,7 @@ golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200505041828-1ed23360d12c/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
@@ -1098,6 +1103,8 @@ gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375/go.mod h1:lNEQeAhU009zbR
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI= gopkg.in/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78= gopkg.in/sourcemap.v1 v1.0.5/go.mod h1:2RlvNNSMglmRrcvhfuzp4hQHwOtjxlbjX7UPY/GXb78=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=

View File

@@ -61,6 +61,7 @@ type ComplexityRoot struct {
IsFacebookLoginEnabled func(childComplexity int) int IsFacebookLoginEnabled func(childComplexity int) int
IsGithubLoginEnabled func(childComplexity int) int IsGithubLoginEnabled func(childComplexity int) int
IsGoogleLoginEnabled func(childComplexity int) int IsGoogleLoginEnabled func(childComplexity int) int
IsMagicLoginEnabled func(childComplexity int) int
IsTwitterLoginEnabled func(childComplexity int) int IsTwitterLoginEnabled func(childComplexity int) int
Version func(childComplexity int) int Version func(childComplexity int) int
} }
@@ -71,6 +72,7 @@ type ComplexityRoot struct {
ForgotPassword func(childComplexity int, params model.ForgotPasswordInput) int ForgotPassword func(childComplexity int, params model.ForgotPasswordInput) int
Login func(childComplexity int, params model.LoginInput) int Login func(childComplexity int, params model.LoginInput) int
Logout func(childComplexity int) int Logout func(childComplexity int) int
MagicLogin func(childComplexity int, params model.MagicLoginInput) int
ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int
ResetPassword func(childComplexity int, params model.ResetPasswordInput) int ResetPassword func(childComplexity int, params model.ResetPasswordInput) int
Signup func(childComplexity int, params model.SignUpInput) int Signup func(childComplexity int, params model.SignUpInput) int
@@ -117,6 +119,7 @@ type ComplexityRoot struct {
type MutationResolver interface { type MutationResolver interface {
Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, error) Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse, error)
Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error) Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, error)
MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Response, error)
Logout(ctx context.Context) (*model.Response, error) Logout(ctx context.Context) (*model.Response, error)
UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error) UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, error)
AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error) AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error)
@@ -226,6 +229,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Meta.IsGoogleLoginEnabled(childComplexity), true return e.complexity.Meta.IsGoogleLoginEnabled(childComplexity), true
case "Meta.isMagicLoginEnabled":
if e.complexity.Meta.IsMagicLoginEnabled == nil {
break
}
return e.complexity.Meta.IsMagicLoginEnabled(childComplexity), true
case "Meta.isTwitterLoginEnabled": case "Meta.isTwitterLoginEnabled":
if e.complexity.Meta.IsTwitterLoginEnabled == nil { if e.complexity.Meta.IsTwitterLoginEnabled == nil {
break break
@@ -295,6 +305,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.Logout(childComplexity), true return e.complexity.Mutation.Logout(childComplexity), true
case "Mutation.magicLogin":
if e.complexity.Mutation.MagicLogin == nil {
break
}
args, err := ec.field_Mutation_magicLogin_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.MagicLogin(childComplexity, args["params"].(model.MagicLoginInput)), true
case "Mutation.resendVerifyEmail": case "Mutation.resendVerifyEmail":
if e.complexity.Mutation.ResendVerifyEmail == nil { if e.complexity.Mutation.ResendVerifyEmail == nil {
break break
@@ -600,6 +622,7 @@ type Meta {
isGithubLoginEnabled: Boolean! isGithubLoginEnabled: Boolean!
isEmailVerificationEnabled: Boolean! isEmailVerificationEnabled: Boolean!
isBasicAuthenticationEnabled: Boolean! isBasicAuthenticationEnabled: Boolean!
isMagicLoginEnabled: Boolean!
} }
type User { type User {
@@ -699,9 +722,15 @@ input DeleteUserInput {
email: String! email: String!
} }
input MagicLoginInput {
email: String!
roles: [String!]
}
type Mutation { type Mutation {
signup(params: SignUpInput!): AuthResponse! signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse! login(params: LoginInput!): AuthResponse!
magicLogin(params: MagicLoginInput!): Response!
logout: Response! logout: Response!
updateProfile(params: UpdateProfileInput!): Response! updateProfile(params: UpdateProfileInput!): Response!
adminUpdateUser(params: AdminUpdateUserInput!): User! adminUpdateUser(params: AdminUpdateUserInput!): User!
@@ -787,6 +816,21 @@ func (ec *executionContext) field_Mutation_login_args(ctx context.Context, rawAr
return args, nil return args, nil
} }
func (ec *executionContext) field_Mutation_magicLogin_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 model.MagicLoginInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalNMagicLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐMagicLoginInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_resendVerifyEmail_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field_Mutation_resendVerifyEmail_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@@ -1376,6 +1420,41 @@ func (ec *executionContext) _Meta_isBasicAuthenticationEnabled(ctx context.Conte
return ec.marshalNBoolean2bool(ctx, field.Selections, res) return ec.marshalNBoolean2bool(ctx, field.Selections, res)
} }
func (ec *executionContext) _Meta_isMagicLoginEnabled(ctx context.Context, field graphql.CollectedField, obj *model.Meta) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Meta",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsMagicLoginEnabled, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_signup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_signup(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -1460,6 +1539,48 @@ func (ec *executionContext) _Mutation_login(ctx context.Context, field graphql.C
return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res) return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res)
} }
func (ec *executionContext) _Mutation_magicLogin(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Mutation",
Field: field,
Args: nil,
IsMethod: true,
IsResolver: true,
}
ctx = graphql.WithFieldContext(ctx, fc)
rawArgs := field.ArgumentMap(ec.Variables)
args, err := ec.field_Mutation_magicLogin_args(ctx, rawArgs)
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
fc.Args = args
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().MagicLogin(rctx, args["params"].(model.MagicLoginInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.Response)
fc.Result = res
return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res)
}
func (ec *executionContext) _Mutation_logout(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Mutation_logout(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@@ -3856,6 +3977,34 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputMagicLoginInput(ctx context.Context, obj interface{}) (model.MagicLoginInput, error) {
var it model.MagicLoginInput
var asMap = obj.(map[string]interface{})
for k, v := range asMap {
switch k {
case "email":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("email"))
it.Email, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Context, obj interface{}) (model.ResendVerifyEmailInput, error) { func (ec *executionContext) unmarshalInputResendVerifyEmailInput(ctx context.Context, obj interface{}) (model.ResendVerifyEmailInput, error) {
var it model.ResendVerifyEmailInput var it model.ResendVerifyEmailInput
var asMap = obj.(map[string]interface{}) var asMap = obj.(map[string]interface{})
@@ -4187,6 +4336,11 @@ func (ec *executionContext) _Meta(ctx context.Context, sel ast.SelectionSet, obj
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "isMagicLoginEnabled":
out.Values[i] = ec._Meta_isMagicLoginEnabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@@ -4223,6 +4377,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "magicLogin":
out.Values[i] = ec._Mutation_magicLogin(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "logout": case "logout":
out.Values[i] = ec._Mutation_logout(ctx, field) out.Values[i] = ec._Mutation_logout(ctx, field)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
@@ -4800,6 +4959,11 @@ func (ec *executionContext) unmarshalNLoginInput2githubᚗcomᚋauthorizerdevᚋ
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)
} }
func (ec *executionContext) unmarshalNMagicLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐMagicLoginInput(ctx context.Context, v interface{}) (model.MagicLoginInput, error) {
res, err := ec.unmarshalInputMagicLoginInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNMeta2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐMeta(ctx context.Context, sel ast.SelectionSet, v model.Meta) graphql.Marshaler { func (ec *executionContext) marshalNMeta2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐMeta(ctx context.Context, sel ast.SelectionSet, v model.Meta) graphql.Marshaler {
return ec._Meta(ctx, sel, &v) return ec._Meta(ctx, sel, &v)
} }

View File

@@ -37,6 +37,11 @@ type LoginInput struct {
Roles []string `json:"roles"` Roles []string `json:"roles"`
} }
type MagicLoginInput struct {
Email string `json:"email"`
Roles []string `json:"roles"`
}
type Meta struct { type Meta struct {
Version string `json:"version"` Version string `json:"version"`
IsGoogleLoginEnabled bool `json:"isGoogleLoginEnabled"` IsGoogleLoginEnabled bool `json:"isGoogleLoginEnabled"`
@@ -45,6 +50,7 @@ type Meta struct {
IsGithubLoginEnabled bool `json:"isGithubLoginEnabled"` IsGithubLoginEnabled bool `json:"isGithubLoginEnabled"`
IsEmailVerificationEnabled bool `json:"isEmailVerificationEnabled"` IsEmailVerificationEnabled bool `json:"isEmailVerificationEnabled"`
IsBasicAuthenticationEnabled bool `json:"isBasicAuthenticationEnabled"` IsBasicAuthenticationEnabled bool `json:"isBasicAuthenticationEnabled"`
IsMagicLoginEnabled bool `json:"isMagicLoginEnabled"`
} }
type ResendVerifyEmailInput struct { type ResendVerifyEmailInput struct {

View File

@@ -13,6 +13,7 @@ type Meta {
isGithubLoginEnabled: Boolean! isGithubLoginEnabled: Boolean!
isEmailVerificationEnabled: Boolean! isEmailVerificationEnabled: Boolean!
isBasicAuthenticationEnabled: Boolean! isBasicAuthenticationEnabled: Boolean!
isMagicLoginEnabled: Boolean!
} }
type User { type User {
@@ -112,9 +113,15 @@ input DeleteUserInput {
email: String! email: String!
} }
input MagicLoginInput {
email: String!
roles: [String!]
}
type Mutation { type Mutation {
signup(params: SignUpInput!): AuthResponse! signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse! login(params: LoginInput!): AuthResponse!
magicLogin(params: MagicLoginInput!): Response!
logout: Response! logout: Response!
updateProfile(params: UpdateProfileInput!): Response! updateProfile(params: UpdateProfileInput!): Response!
adminUpdateUser(params: AdminUpdateUserInput!): User! adminUpdateUser(params: AdminUpdateUserInput!): User!

View File

@@ -19,6 +19,10 @@ func (r *mutationResolver) Login(ctx context.Context, params model.LoginInput) (
return resolvers.Login(ctx, params) return resolvers.Login(ctx, params)
} }
func (r *mutationResolver) MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Response, error) {
return resolvers.MagicLogin(ctx, params)
}
func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) { func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) {
return resolvers.Logout(ctx) return resolvers.Logout(ctx)
} }

View File

@@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@@ -15,36 +16,50 @@ import (
"github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/oauth"
"github.com/authorizerdev/authorizer/server/session" "github.com/authorizerdev/authorizer/server/session"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/coreos/go-oidc/v3/oidc"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"golang.org/x/oauth2" "golang.org/x/oauth2"
) )
func processGoogleUserInfo(code string) (db.User, error) { func processGoogleUserInfo(code string) (db.User, error) {
user := db.User{} user := db.User{}
token, err := oauth.OAuthProvider.GoogleConfig.Exchange(oauth2.NoContext, code) ctx := context.Background()
oauth2Token, err := oauth.OAuthProviders.GoogleConfig.Exchange(ctx, code)
if err != nil { if err != nil {
return user, fmt.Errorf("invalid google exchange code: %s", err.Error()) 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) verifier := oauth.OIDCProviders.GoogleOIDC.Verifier(&oidc.Config{ClientID: oauth.OAuthProviders.GoogleConfig.ClientID})
if err != nil {
return user, err // 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() // Parse and verify ID Token payload.
body, err := ioutil.ReadAll(response.Body) idToken, err := verifier.Verify(ctx, rawIDToken)
if err != nil { 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:", err.Error())
} }
userRawData := make(map[string]string) // Extract custom claims
json.Unmarshal(body, &userRawData) var claims struct {
Email string `json:"email"`
Picture string `json:"picture"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Verified bool `json:"email_verified"`
}
if err := idToken.Claims(&claims); err != nil {
return user, fmt.Errorf("unable to extract claims")
}
user = db.User{ user = db.User{
FirstName: userRawData["given_name"], FirstName: claims.GivenName,
LastName: userRawData["family_name"], LastName: claims.FamilyName,
Image: userRawData["picture"], Image: claims.Picture,
Email: userRawData["email"], Email: claims.Email,
EmailVerifiedAt: time.Now().Unix(), EmailVerifiedAt: time.Now().Unix(),
} }
@@ -53,7 +68,7 @@ func processGoogleUserInfo(code string) (db.User, error) {
func processGithubUserInfo(code string) (db.User, error) { func processGithubUserInfo(code string) (db.User, error) {
user := db.User{} user := db.User{}
token, err := oauth.OAuthProvider.GithubConfig.Exchange(oauth2.NoContext, code) token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
if err != nil { if err != nil {
return user, fmt.Errorf("invalid github exchange code: %s", err.Error()) return user, fmt.Errorf("invalid github exchange code: %s", err.Error())
} }
@@ -102,7 +117,7 @@ func processGithubUserInfo(code string) (db.User, error) {
func processFacebookUserInfo(code string) (db.User, error) { func processFacebookUserInfo(code string) (db.User, error) {
user := db.User{} user := db.User{}
token, err := oauth.OAuthProvider.FacebookConfig.Exchange(oauth2.NoContext, code) token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
if err != nil { if err != nil {
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error()) return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
} }
@@ -208,7 +223,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
signupMethod := existingUser.SignupMethod signupMethod := existingUser.SignupMethod
if !strings.Contains(signupMethod, provider) { if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + enum.Github.String() signupMethod = signupMethod + "," + provider
} }
user.SignupMethod = signupMethod user.SignupMethod = signupMethod
user.Password = existingUser.Password user.Password = existingUser.Password

View File

@@ -48,28 +48,46 @@ func OAuthLoginHandler() gin.HandlerFunc {
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
provider := c.Param("oauth_provider") provider := c.Param("oauth_provider")
isProviderConfigured := true
switch provider { switch provider {
case enum.Google.String(): case enum.Google.String():
if oauth.OAuthProviders.GoogleConfig == nil {
isProviderConfigured = false
break
}
session.SetSocailLoginState(oauthStateString, enum.Google.String()) session.SetSocailLoginState(oauthStateString, enum.Google.String())
// during the init of OAuthProvider authorizer url might be empty // during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProvider.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google" oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google"
url := oauth.OAuthProvider.GoogleConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Github.String(): case enum.Github.String():
if oauth.OAuthProviders.GithubConfig == nil {
isProviderConfigured = false
break
}
session.SetSocailLoginState(oauthStateString, enum.Github.String()) session.SetSocailLoginState(oauthStateString, enum.Github.String())
oauth.OAuthProvider.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github" oauth.OAuthProviders.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github"
url := oauth.OAuthProvider.GithubConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Facebook.String(): case enum.Facebook.String():
if oauth.OAuthProviders.FacebookConfig == nil {
isProviderConfigured = false
break
}
session.SetSocailLoginState(oauthStateString, enum.Facebook.String()) session.SetSocailLoginState(oauthStateString, enum.Facebook.String())
oauth.OAuthProvider.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook" oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook"
url := oauth.OAuthProvider.FacebookConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
default: default:
c.JSON(422, gin.H{ c.JSON(422, gin.H{
"message": "Invalid oauth provider", "message": "Invalid oauth provider",
}) })
} }
if !isProviderConfigured {
c.JSON(422, gin.H{
"message": provider + " not configured",
})
}
} }
} }

View File

@@ -46,7 +46,9 @@ func VerifyEmailHandler() gin.HandlerFunc {
} }
// update email_verified_at in users table // update email_verified_at in users table
db.Mgr.UpdateVerificationTime(time.Now().Unix(), user.ID) if user.EmailVerifiedAt <= 0 {
db.Mgr.UpdateVerificationTime(time.Now().Unix(), user.ID)
}
// delete from verification table // delete from verification table
db.Mgr.DeleteToken(claim.Email) db.Mgr.DeleteToken(claim.Email)

View File

@@ -1,33 +1,49 @@
package oauth package oauth
import ( import (
"context"
"log"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/coreos/go-oidc/v3/oidc"
"golang.org/x/oauth2" "golang.org/x/oauth2"
facebookOAuth2 "golang.org/x/oauth2/facebook" facebookOAuth2 "golang.org/x/oauth2/facebook"
githubOAuth2 "golang.org/x/oauth2/github" githubOAuth2 "golang.org/x/oauth2/github"
googleOAuth2 "golang.org/x/oauth2/google"
) )
type OAuthProviders struct { type OAuthProvider struct {
GoogleConfig *oauth2.Config GoogleConfig *oauth2.Config
GithubConfig *oauth2.Config GithubConfig *oauth2.Config
FacebookConfig *oauth2.Config FacebookConfig *oauth2.Config
} }
var OAuthProvider OAuthProviders type OIDCProvider struct {
GoogleOIDC *oidc.Provider
}
var (
OAuthProviders OAuthProvider
OIDCProviders OIDCProvider
)
func InitOAuth() { func InitOAuth() {
ctx := context.Background()
if constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "" { if constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "" {
OAuthProvider.GoogleConfig = &oauth2.Config{ p, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
log.Fatalln("error creating oidc provider for google:", err)
}
OIDCProviders.GoogleOIDC = p
OAuthProviders.GoogleConfig = &oauth2.Config{
ClientID: constants.GOOGLE_CLIENT_ID, ClientID: constants.GOOGLE_CLIENT_ID,
ClientSecret: constants.GOOGLE_CLIENT_SECRET, ClientSecret: constants.GOOGLE_CLIENT_SECRET,
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/google", RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/google",
Endpoint: googleOAuth2.Endpoint, Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
Scopes: []string{"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"}, Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
} }
} }
if constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "" { if constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "" {
OAuthProvider.GithubConfig = &oauth2.Config{ OAuthProviders.GithubConfig = &oauth2.Config{
ClientID: constants.GITHUB_CLIENT_ID, ClientID: constants.GITHUB_CLIENT_ID,
ClientSecret: constants.GITHUB_CLIENT_SECRET, ClientSecret: constants.GITHUB_CLIENT_SECRET,
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/github", RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/github",
@@ -35,7 +51,7 @@ func InitOAuth() {
} }
} }
if constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "" { if constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "" {
OAuthProvider.FacebookConfig = &oauth2.Config{ OAuthProviders.FacebookConfig = &oauth2.Config{
ClientID: constants.FACEBOOK_CLIENT_ID, ClientID: constants.FACEBOOK_CLIENT_ID,
ClientSecret: constants.FACEBOOK_CLIENT_SECRET, ClientSecret: constants.FACEBOOK_CLIENT_SECRET,
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/facebook", RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/facebook",

View File

@@ -0,0 +1,122 @@
package resolvers
import (
"context"
"fmt"
"log"
"strings"
"time"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/utils"
)
func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Response, error) {
var res *model.Response
if constants.DISABLE_MAGIC_LOGIN == "true" {
return res, fmt.Errorf(`magic link login is disabled for this instance`)
}
params.Email = strings.ToLower(params.Email)
if !utils.IsValidEmail(params.Email) {
return res, fmt.Errorf(`invalid email address`)
}
inputRoles := []string{}
user := db.User{
Email: params.Email,
}
// find user with email
existingUser, err := db.Mgr.GetUserByEmail(params.Email)
if err != nil {
user.SignupMethod = enum.MagicLink.String()
// define roles for new user
if len(params.Roles) > 0 {
// check if roles exists
if !utils.IsValidRoles(constants.ROLES, params.Roles) {
return res, fmt.Errorf(`invalid roles`)
} else {
inputRoles = params.Roles
}
} else {
inputRoles = constants.DEFAULT_ROLES
}
user.Roles = strings.Join(inputRoles, ",")
} else {
user = existingUser
// There multiple scenarios with roles here in magic link 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 {
return res, fmt.Errorf(`invalid roles`)
} else {
user.Roles = existingUser.Roles + "," + strings.Join(unasignedRoles, ",")
}
} else {
user.Roles = existingUser.Roles
}
signupMethod := existingUser.SignupMethod
if !strings.Contains(signupMethod, enum.MagicLink.String()) {
signupMethod = signupMethod + "," + enum.MagicLink.String()
}
user.SignupMethod = signupMethod
}
user, _ = db.Mgr.SaveUser(user)
if constants.DISABLE_EMAIL_VERIFICATION != "true" {
// insert verification request
verificationType := enum.MagicLink.String()
token, err := utils.CreateVerificationToken(params.Email, verificationType)
if err != nil {
log.Println(`Error generating token`, err)
}
db.Mgr.AddVerification(db.VerificationRequest{
Token: token,
Identifier: verificationType,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email,
})
// exec it as go routin so that we can reduce the api latency
go func() {
utils.SendVerificationMail(params.Email, token)
}()
}
res = &model.Response{
Message: `Magic Link has been sent to your email. Please check your inbox!`,
}
return res, nil
}

View File

@@ -84,14 +84,16 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
AccessToken: &token, AccessToken: &token,
AccessTokenExpiresAt: &expiresAt, AccessTokenExpiresAt: &expiresAt,
User: &model.User{ User: &model.User{
ID: userIdStr, ID: userIdStr,
Email: user.Email, Email: user.Email,
Image: &user.Image, Image: &user.Image,
FirstName: &user.FirstName, FirstName: &user.FirstName,
LastName: &user.LastName, LastName: &user.LastName,
Roles: strings.Split(user.Roles, ","), Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt, CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt, UpdatedAt: &user.UpdatedAt,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
}, },
} }
return res, nil return res, nil

View File

@@ -13,7 +13,7 @@ import (
"github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/enum"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
v8 "rogchap.com/v8go" "github.com/robertkrimen/otto"
) )
func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) { func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) {
@@ -50,25 +50,24 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
"signUpMethods": strings.Split(user.SignupMethod, ","), "signUpMethods": strings.Split(user.SignupMethod, ","),
} }
ctx, _ := v8.NewContext() vm := otto.New()
userBytes, _ := json.Marshal(userInfo) userBytes, _ := json.Marshal(userInfo)
claimBytes, _ := json.Marshal(customClaims) claimBytes, _ := json.Marshal(customClaims)
ctx.RunScript(fmt.Sprintf(` vm.Run(fmt.Sprintf(`
const user = %s; var user = %s;
const tokenPayload = %s; var tokenPayload = %s;
const customFunction = %s; var customFunction = %s;
const functionRes = JSON.stringify(customFunction(user, tokenPayload)); var functionRes = JSON.stringify(customFunction(user, tokenPayload));
`, string(userBytes), string(claimBytes), accessTokenScript), "functionCall.js") `, string(userBytes), string(claimBytes), accessTokenScript))
val, err := ctx.RunScript("functionRes", "functionRes.js") val, err := vm.Get("functionRes")
if err != nil { if err != nil {
log.Println("=> err custom access token script:", err) log.Println("=> err custom access token script:", err)
} else { } else {
extraPayload := make(map[string]interface{}) extraPayload := make(map[string]interface{})
err = json.Unmarshal([]byte(fmt.Sprintf("%s", val)), &extraPayload) err = json.Unmarshal([]byte(fmt.Sprintf("%s", val)), &extraPayload)
if err != nil { if err != nil {
log.Println("Error converting accessTokenScript response to map:", err) log.Println("Error converting accessTokenScript response to map:", err)
} else { } else {

View File

@@ -74,7 +74,7 @@ func SendVerificationMail(toEmail, token string) error {
<tr style="background: rgb(249,250,251);padding: 10px;margin-bottom:10px;border-radius:5px;"> <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;"> <td class="esd-block-text es-m-txt-c es-p15t" align="center" style="padding:10px;padding-bottom:30px;">
<p>Hey there 👋</p> <p>Hey there 👋</p>
<p>We received a request to sign-up for <b>%s</b>. If this is correct, please confirm your email address by clicking the button below.</p> <br/> <p>We received a request to sign-up / login for <b>%s</b>. If this is correct, please confirm your email address by clicking the button below.</p> <br/>
<a href="%s" 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> <a href="%s" 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> </td>
</tr> </tr>

View File

@@ -16,5 +16,6 @@ func GetMetaInfo() model.Meta {
IsTwitterLoginEnabled: constants.TWITTER_CLIENT_ID != "" && constants.TWITTER_CLIENT_SECRET != "", IsTwitterLoginEnabled: constants.TWITTER_CLIENT_ID != "" && constants.TWITTER_CLIENT_SECRET != "",
IsBasicAuthenticationEnabled: constants.DISABLE_BASIC_AUTHENTICATION != "true", IsBasicAuthenticationEnabled: constants.DISABLE_BASIC_AUTHENTICATION != "true",
IsEmailVerificationEnabled: constants.DISABLE_EMAIL_VERIFICATION != "true", IsEmailVerificationEnabled: constants.DISABLE_EMAIL_VERIFICATION != "true",
IsMagicLoginEnabled: constants.DISABLE_MAGIC_LOGIN != "true" && constants.DISABLE_EMAIL_VERIFICATION != "true",
} }
} }