Compare commits

...

21 Commits

Author SHA1 Message Date
Lakhan Samani
155d2e65c2 fix: use char(36) with golang uuid instead of sql uuid type (#78)
resolves #77
2021-12-14 22:57:45 +05:30
Lakhan Samani
4d341e9876 feat: add instruction for railway.app 2021-12-11 23:07:19 +05:30
Lakhan Samani
1761f41691 fix: read cookieName-client if cookie with cookieName is not present 2021-12-11 06:45:15 +05:30
Lakhan Samani
00565c8717 Fix/cookie host (#76)
* fix: cookie host

* feat: add test for url utils

* fix: url test

* fix: multi domain cookie if allowed
2021-12-11 06:41:35 +05:30
Lakhan Samani
74a551ae09 Update README.md 2021-12-09 09:17:32 +05:30
Lakhan Samani
cb5b02d777 fix: update discord link
fix: redirect link for verification handler (#74)

Resolves #70
2021-12-08 18:10:11 +05:30
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
Lakhan Samani
3ed5426467 chore: fix env for release 2021-10-28 07:46:56 +05:30
Lakhan Samani
29251c8c20 fix: use inbuilt actions 2021-10-28 07:41:49 +05:30
Lakhan Samani
08b1f97ccb chore: fix docker build 2021-10-28 07:17:44 +05:30
Lakhan Samani
abc2991e1c Merge branch 'main' of https://github.com/authorizerdev/authorizer 2021-10-27 23:16:15 +05:30
Lakhan Samani
b69d0b8e23 Feat/multiple session (#64)
* fix: disable windows build

* feat: add ability to handle multiple sessions
2021-10-27 23:15:38 +05:30
Lakhan Samani
86d781b210 fix: disable windows build 2021-10-27 12:10:44 +05:30
49 changed files with 944 additions and 1069 deletions

View File

@@ -9,3 +9,4 @@ ROLES=user
DEFAULT_ROLES=user
PROTECTED_ROLES=admin
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

@@ -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

View File

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

View File

@@ -5,3 +5,5 @@ cmd:
cd server && go build -ldflags "-w -X main.Version=$(VERSION)" -o '../build/server'
clean:
rm -rf build
test:
cd server && go test ./...

View File

@@ -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,19 @@ Deploy Authorizer using [heroku](https://github.com/authorizerdev/authorizer-her
<br/><br/>
[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](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/>
[![Deploy on Railway](https://railway.app/button.svg)](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.
## Integrating into your website
@@ -197,3 +188,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>

View File

@@ -1,5 +1,11 @@
# Task List
## Feature Multiple sessions
- Multiple sessions for users to login use hMset from redis for this
user_id access_token1 long_live_token1
user_id access_token2 long_live_token2
# Feature roles
For the first version we will only support setting roles master list via env

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",
"license": "ISC",
"dependencies": {
"@authorizerdev/authorizer-react": "^0.1.0-beta.19",
"@authorizerdev/authorizer-react": "^0.1.0",
"@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"
},

View File

@@ -14,6 +14,7 @@ var (
JWT_SECRET = ""
ALLOWED_ORIGINS = []string{}
AUTHORIZER_URL = ""
APP_URL = ""
PORT = "8080"
REDIS_URL = ""
IS_PROD = false
@@ -21,6 +22,7 @@ var (
RESET_PASSWORD_URL = ""
DISABLE_EMAIL_VERIFICATION = "false"
DISABLE_BASIC_AUTHENTICATION = "false"
DISABLE_MAGIC_LOGIN = "false"
// ROLES
ROLES = []string{}

View File

@@ -27,6 +27,7 @@ type Manager interface {
GetVerificationByEmail(email string) (VerificationRequest, error)
DeleteUser(email string) error
SaveRoles(roles []Role) error
SaveSession(session Session) error
}
type manager struct {
@@ -56,7 +57,7 @@ func InitDB() {
if err != nil {
log.Fatal("Failed to init db:", err)
} else {
db.AutoMigrate(&User{}, &VerificationRequest{}, &Role{})
db.AutoMigrate(&User{}, &VerificationRequest{}, &Role{}, &Session{})
}
Mgr = &manager{db: db}

View File

@@ -9,7 +9,7 @@ import (
)
type Role struct {
ID uuid.UUID `gorm:"type:uuid;"`
ID uuid.UUID `gorm:"primaryKey;type:char(36)"`
Role string `gorm:"unique"`
}

39
server/db/session.go Normal file
View File

@@ -0,0 +1,39 @@
package db
import (
"log"
"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
type Session struct {
ID uuid.UUID `gorm:"primaryKey;type:char(36)"`
UserID uuid.UUID `gorm:"type:char(36)"`
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
}

View File

@@ -10,16 +10,16 @@ import (
)
type User struct {
ID uuid.UUID `gorm:"type:uuid;"`
ID uuid.UUID `gorm:"primaryKey;type:char(36)"`
FirstName string
LastName string
Email string `gorm:"unique"`
Password string
Password string `gorm:"type:text"`
SignupMethod string
EmailVerifiedAt int64
CreatedAt int64 `gorm:"autoCreateTime"`
UpdatedAt int64 `gorm:"autoUpdateTime"`
Image string
Image string `gorm:"type:text"`
Roles string
}

View File

@@ -9,8 +9,8 @@ import (
)
type VerificationRequest struct {
ID uuid.UUID `gorm:"type:uuid;"`
Token string `gorm:"index"`
ID uuid.UUID `gorm:"primaryKey;type:char(36)"`
Token string `gorm:"type:text"`
Identifier string
ExpiresAt int64
CreatedAt int64 `gorm:"autoCreateTime"`
@@ -30,6 +30,7 @@ func (mgr *manager) AddVerification(verification VerificationRequest) (Verificat
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

View File

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

View File

@@ -4,6 +4,7 @@ go 1.16
require (
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-gonic/gin v1.7.2
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/mattn/go-isatty v0.0.13 // 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/vektah/gqlparser/v2 v2.1.0
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
@@ -28,5 +30,4 @@ require (
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
)

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/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/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-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=
@@ -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/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-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 v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
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-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-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-20200513185701-a91f0712d120/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/sourcemap.v1 v1.0.5 h1:inv58fC9f9J3TK2Y2R1NPntXEn3/wjWHkonhIUODNTI=
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/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
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
IsGithubLoginEnabled func(childComplexity int) int
IsGoogleLoginEnabled func(childComplexity int) int
IsMagicLoginEnabled func(childComplexity int) int
IsTwitterLoginEnabled func(childComplexity int) int
Version func(childComplexity int) int
}
@@ -71,6 +72,7 @@ type ComplexityRoot struct {
ForgotPassword func(childComplexity int, params model.ForgotPasswordInput) int
Login func(childComplexity int, params model.LoginInput) int
Logout func(childComplexity int) int
MagicLogin func(childComplexity int, params model.MagicLoginInput) int
ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int
ResetPassword func(childComplexity int, params model.ResetPasswordInput) int
Signup func(childComplexity int, params model.SignUpInput) int
@@ -117,6 +119,7 @@ type ComplexityRoot struct {
type MutationResolver interface {
Signup(ctx context.Context, params model.SignUpInput) (*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)
UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model.Response, 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
case "Meta.isMagicLoginEnabled":
if e.complexity.Meta.IsMagicLoginEnabled == nil {
break
}
return e.complexity.Meta.IsMagicLoginEnabled(childComplexity), true
case "Meta.isTwitterLoginEnabled":
if e.complexity.Meta.IsTwitterLoginEnabled == nil {
break
@@ -295,6 +305,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
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":
if e.complexity.Mutation.ResendVerifyEmail == nil {
break
@@ -600,6 +622,7 @@ type Meta {
isGithubLoginEnabled: Boolean!
isEmailVerificationEnabled: Boolean!
isBasicAuthenticationEnabled: Boolean!
isMagicLoginEnabled: Boolean!
}
type User {
@@ -699,9 +722,15 @@ input DeleteUserInput {
email: String!
}
input MagicLoginInput {
email: String!
roles: [String!]
}
type Mutation {
signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse!
magicLogin(params: MagicLoginInput!): Response!
logout: Response!
updateProfile(params: UpdateProfileInput!): Response!
adminUpdateUser(params: AdminUpdateUserInput!): User!
@@ -787,6 +816,21 @@ func (ec *executionContext) field_Mutation_login_args(ctx context.Context, rawAr
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) {
var err error
args := map[string]interface{}{}
@@ -1376,6 +1420,41 @@ func (ec *executionContext) _Meta_isBasicAuthenticationEnabled(ctx context.Conte
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) {
defer func() {
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)
}
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) {
defer func() {
if r := recover(); r != nil {
@@ -3856,6 +3977,34 @@ func (ec *executionContext) unmarshalInputLoginInput(ctx context.Context, obj in
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) {
var it model.ResendVerifyEmailInput
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 {
invalids++
}
case "isMagicLoginEnabled":
out.Values[i] = ec._Meta_isMagicLoginEnabled(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
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 {
invalids++
}
case "magicLogin":
out.Values[i] = ec._Mutation_magicLogin(ctx, field)
if out.Values[i] == graphql.Null {
invalids++
}
case "logout":
out.Values[i] = ec._Mutation_logout(ctx, field)
if out.Values[i] == graphql.Null {
@@ -4800,6 +4959,11 @@ func (ec *executionContext) unmarshalNLoginInput2githubᚗcomᚋauthorizerdevᚋ
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 {
return ec._Meta(ctx, sel, &v)
}

View File

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

View File

@@ -13,6 +13,7 @@ type Meta {
isGithubLoginEnabled: Boolean!
isEmailVerificationEnabled: Boolean!
isBasicAuthenticationEnabled: Boolean!
isMagicLoginEnabled: Boolean!
}
type User {
@@ -112,9 +113,15 @@ input DeleteUserInput {
email: String!
}
input MagicLoginInput {
email: String!
roles: [String!]
}
type Mutation {
signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse!
magicLogin(params: MagicLoginInput!): Response!
logout: Response!
updateProfile(params: UpdateProfileInput!): Response!
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)
}
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) {
return resolvers.Logout(ctx)
}

View File

@@ -1,6 +1,7 @@
package handlers
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
@@ -15,36 +16,50 @@ import (
"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)
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)
// Extract custom claims
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{
FirstName: userRawData["given_name"],
LastName: userRawData["family_name"],
Image: userRawData["picture"],
Email: userRawData["email"],
FirstName: claims.GivenName,
LastName: claims.FamilyName,
Image: claims.Picture,
Email: claims.Email,
EmailVerifiedAt: time.Now().Unix(),
}
@@ -53,7 +68,7 @@ func processGoogleUserInfo(code string) (db.User, error) {
func processGithubUserInfo(code string) (db.User, error) {
user := db.User{}
token, err := oauth.OAuthProvider.GithubConfig.Exchange(oauth2.NoContext, code)
token, err := oauth.OAuthProviders.GithubConfig.Exchange(oauth2.NoContext, code)
if err != nil {
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) {
user := db.User{}
token, err := oauth.OAuthProvider.FacebookConfig.Exchange(oauth2.NoContext, code)
token, err := oauth.OAuthProviders.FacebookConfig.Exchange(oauth2.NoContext, code)
if err != nil {
return user, fmt.Errorf("invalid facebook exchange code: %s", err.Error())
}
@@ -147,11 +162,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
provider := c.Param("oauth_provider")
state := c.Request.FormValue("state")
sessionState := session.GetToken(state)
sessionState := session.GetSocailLoginState(state)
if sessionState == "" {
c.JSON(400, gin.H{"error": "invalid oauth state"})
}
session.DeleteToken(sessionState)
session.RemoveSocialLoginState(state)
// contains random token, redirect url, role
sessionSplit := strings.Split(state, "___")
@@ -208,7 +223,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
signupMethod := existingUser.SignupMethod
if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + enum.Github.String()
signupMethod = signupMethod + "," + provider
}
user.SignupMethod = signupMethod
user.Password = existingUser.Password
@@ -254,7 +269,16 @@ func OAuthCallbackHandler() gin.HandlerFunc {
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, inputRoles)
utils.SetCookie(c, accessToken)
session.SetToken(userIdStr, refreshToken)
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)
}

View File

@@ -48,28 +48,46 @@ func OAuthLoginHandler() gin.HandlerFunc {
oauthStateString := uuid.String() + "___" + redirectURL + "___" + roles
provider := c.Param("oauth_provider")
isProviderConfigured := true
switch provider {
case enum.Google.String():
session.SetToken(oauthStateString, enum.Google.String())
if oauth.OAuthProviders.GoogleConfig == nil {
isProviderConfigured = false
break
}
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)
oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google"
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Github.String():
session.SetToken(oauthStateString, enum.Github.String())
oauth.OAuthProvider.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github"
url := oauth.OAuthProvider.GithubConfig.AuthCodeURL(oauthStateString)
if oauth.OAuthProviders.GithubConfig == nil {
isProviderConfigured = false
break
}
session.SetSocailLoginState(oauthStateString, enum.Github.String())
oauth.OAuthProviders.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github"
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Facebook.String():
session.SetToken(oauthStateString, enum.Github.String())
oauth.OAuthProvider.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook"
url := oauth.OAuthProvider.FacebookConfig.AuthCodeURL(oauthStateString)
if oauth.OAuthProviders.FacebookConfig == nil {
isProviderConfigured = false
break
}
session.SetSocailLoginState(oauthStateString, enum.Facebook.String())
oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/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",
})
}
}
}

View File

@@ -46,7 +46,9 @@ func VerifyEmailHandler() gin.HandlerFunc {
}
// update email_verified_at in users table
if user.EmailVerifiedAt <= 0 {
db.Mgr.UpdateVerificationTime(time.Now().Unix(), user.ID)
}
// delete from verification table
db.Mgr.DeleteToken(claim.Email)
@@ -56,8 +58,17 @@ func VerifyEmailHandler() gin.HandlerFunc {
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, refreshToken)
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)
}()
utils.SetCookie(c, accessToken)
c.Redirect(http.StatusTemporaryRedirect, claim.Host)
c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL)
}
}

View File

@@ -32,6 +32,8 @@ func GinContextToContextMiddleware() gin.HandlerFunc {
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
constants.APP_URL = origin
log.Println("=> APP_URL:", constants.APP_URL)
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)
c.Writer.Header().Set("Access-Control-Allow-Credentials", "true")
c.Writer.Header().Set("Access-Control-Allow-Headers", "Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization, accept, origin, Cache-Control, X-Requested-With")

View File

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

View File

@@ -60,7 +60,7 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
return res, fmt.Errorf("user with this email address already exists")
}
session.DeleteToken(fmt.Sprintf("%v", user.ID))
session.DeleteUserSession(fmt.Sprintf("%v", user.ID))
utils.DeleteCookie(gc)
user.Email = newEmail
@@ -100,7 +100,7 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
rolesToSave = strings.Join(inputRoles, ",")
}
session.DeleteToken(fmt.Sprintf("%v", user.ID))
session.DeleteUserSession(fmt.Sprintf("%v", user.ID))
utils.DeleteCookie(gc)
}

View File

@@ -27,7 +27,7 @@ func DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Respo
return res, err
}
session.DeleteToken(fmt.Sprintf("%x", user.ID))
session.DeleteUserSession(fmt.Sprintf("%x", user.ID))
err = db.Mgr.DeleteUser(params.Email)
if err != nil {

View File

@@ -60,7 +60,16 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, refreshToken)
session.SetToken(userIdStr, accessToken, refreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.SaveSession(sessionData)
}()
res = &model.AuthResponse{
Message: `Logged in successfully`,

View File

@@ -27,7 +27,7 @@ func Logout(ctx context.Context) (*model.Response, error) {
}
userId := fmt.Sprintf("%v", claim["id"])
session.DeleteToken(userId)
session.DeleteToken(userId, token)
res = &model.Response{
Message: "Logged out successfully",
}

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

@@ -30,7 +30,7 @@ func Profile(ctx context.Context) (*model.User, error) {
userID := fmt.Sprintf("%v", claim["id"])
email := fmt.Sprintf("%v", claim["email"])
sessionToken := session.GetToken(userID)
sessionToken := session.GetToken(userID, token)
if sessionToken == "" {
return res, fmt.Errorf(`unauthorized`)

View File

@@ -127,7 +127,16 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, refreshToken)
session.SetToken(userIdStr, accessToken, refreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.SaveSession(sessionData)
}()
res = &model.AuthResponse{
Message: `Signed up successfully.`,
AccessToken: &accessToken,

View File

@@ -37,7 +37,7 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
userIdStr := fmt.Sprintf("%v", user.ID)
sessionToken := session.GetToken(userIdStr)
sessionToken := session.GetToken(userIdStr, token)
if sessionToken == "" {
return res, fmt.Errorf(`unauthorized`)
@@ -63,7 +63,19 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
if accessTokenErr != nil || expiresTimeObj.Sub(currentTimeObj).Minutes() <= 5 {
// if access token has expired and refresh/session token is valid
// generate new accessToken
currentRefreshToken := session.GetToken(userIdStr, token)
session.DeleteToken(userIdStr, token)
token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRoles)
session.SetToken(userIdStr, token, currentRefreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.SaveSession(sessionData)
}()
}
utils.SetCookie(gc, token)
@@ -80,6 +92,8 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
},
}
return res, nil

View File

@@ -33,7 +33,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
}
id := fmt.Sprintf("%v", claim["id"])
sessionToken := session.GetToken(id)
sessionToken := session.GetToken(id, token)
if sessionToken == "" {
return res, fmt.Errorf(`unauthorized`)
@@ -99,7 +99,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
return res, fmt.Errorf("user with this email address already exists")
}
session.DeleteToken(fmt.Sprintf("%v", user.ID))
session.DeleteUserSession(fmt.Sprintf("%v", user.ID))
utils.DeleteCookie(gc)
user.Email = newEmail

View File

@@ -47,7 +47,16 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, refreshToken)
session.SetToken(userIdStr, accessToken, refreshToken)
go func() {
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.SaveSession(sessionData)
}()
res = &model.AuthResponse{
Message: `Email verified successfully.`,

View File

@@ -1,41 +1,88 @@
package session
import "sync"
import (
"log"
"sync"
)
type InMemoryStore struct {
mu sync.Mutex
store map[string]string
store map[string]map[string]string
socialLoginState map[string]string
}
func (c *InMemoryStore) AddToken(userId, token string) {
func (c *InMemoryStore) AddToken(userId, accessToken, refreshToken string) {
c.mu.Lock()
// delete sessions > 500 // not recommended for production
if len(c.store) >= 500 {
c.store = make(map[string]string)
c.store = map[string]map[string]string{}
}
c.store[userId] = token
// check if entry exists in map
_, exists := c.store[userId]
if exists {
tempMap := c.store[userId]
tempMap[accessToken] = refreshToken
c.store[userId] = tempMap
} else {
tempMap := map[string]string{
accessToken: refreshToken,
}
c.store[userId] = tempMap
}
log.Println(c.store)
c.mu.Unlock()
}
func (c *InMemoryStore) DeleteToken(userId string) {
func (c *InMemoryStore) DeleteUserSession(userId string) {
c.mu.Lock()
delete(c.store, userId)
c.mu.Unlock()
}
func (c *InMemoryStore) ClearStore() {
func (c *InMemoryStore) DeleteToken(userId, accessToken string) {
c.mu.Lock()
c.store = make(map[string]string)
delete(c.store[userId], accessToken)
c.mu.Unlock()
}
func (c *InMemoryStore) GetToken(userId string) string {
func (c *InMemoryStore) ClearStore() {
c.mu.Lock()
c.store = map[string]map[string]string{}
c.mu.Unlock()
}
func (c *InMemoryStore) GetToken(userId, accessToken string) string {
token := ""
c.mu.Lock()
if val, ok := c.store[userId]; ok {
if sessionMap, ok := c.store[userId]; ok {
if val, ok := sessionMap[accessToken]; ok {
token = val
}
}
c.mu.Unlock()
return token
}
func (c *InMemoryStore) SetSocialLoginState(key, state string) {
c.mu.Lock()
c.socialLoginState[key] = state
c.mu.Unlock()
}
func (c *InMemoryStore) GetSocialLoginState(key string) string {
state := ""
if stateVal, ok := c.socialLoginState[key]; ok {
state = stateVal
}
return state
}
func (c *InMemoryStore) RemoveSocialLoginState(key string) {
c.mu.Lock()
delete(c.socialLoginState, key)
c.mu.Unlock()
}

View File

@@ -2,6 +2,7 @@ package session
import (
"context"
"fmt"
"log"
"github.com/go-redis/redis/v8"
@@ -12,20 +13,29 @@ type RedisStore struct {
store *redis.Client
}
func (c *RedisStore) AddToken(userId, token string) {
err := c.store.Set(c.ctx, "authorizer_"+userId, token, 0).Err()
func (c *RedisStore) AddToken(userId, accessToken, refreshToken string) {
err := c.store.HMSet(c.ctx, "authorizer_"+userId, map[string]string{
accessToken: refreshToken,
}).Err()
if err != nil {
log.Fatalln("Error saving redis token:", err)
}
}
func (c *RedisStore) DeleteToken(userId string) {
func (c *RedisStore) DeleteUserSession(userId string) {
err := c.store.Del(c.ctx, "authorizer_"+userId).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}
}
func (c *RedisStore) DeleteToken(userId, accessToken string) {
err := c.store.HDel(c.ctx, "authorizer_"+userId, accessToken).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}
}
func (c *RedisStore) ClearStore() {
err := c.store.Del(c.ctx, "authorizer_*").Err()
if err != nil {
@@ -33,11 +43,38 @@ func (c *RedisStore) ClearStore() {
}
}
func (c *RedisStore) GetToken(userId string) string {
func (c *RedisStore) GetToken(userId, accessToken string) string {
token := ""
token, err := c.store.Get(c.ctx, "authorizer_"+userId).Result()
res, err := c.store.HMGet(c.ctx, "authorizer_"+userId, accessToken).Result()
if err != nil {
log.Println("Error getting token from redis store:", err)
}
if len(res) > 0 && res[0] != nil {
token = fmt.Sprintf("%v", res[0])
}
return token
}
func (c *RedisStore) SetSocialLoginState(key, state string) {
err := c.store.Set(c.ctx, key, state, 0).Err()
if err != nil {
log.Fatalln("Error saving redis token:", err)
}
}
func (c *RedisStore) GetSocialLoginState(key string) string {
state := ""
state, err := c.store.Get(c.ctx, key).Result()
if err != nil {
log.Println("Error getting token from redis store:", err)
}
return state
}
func (c *RedisStore) RemoveSocialLoginState(key string) {
err := c.store.Del(c.ctx, key).Err()
if err != nil {
log.Fatalln("Error deleting redis token:", err)
}
}

View File

@@ -15,30 +15,42 @@ type SessionStore struct {
var SessionStoreObj SessionStore
func SetToken(userId, token string) {
func SetToken(userId, accessToken, refreshToken string) {
// TODO: Set session information in db for all the sessions that gets generated
// it should async go function
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.AddToken(userId, token)
SessionStoreObj.RedisMemoryStoreObj.AddToken(userId, accessToken, refreshToken)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.AddToken(userId, token)
SessionStoreObj.InMemoryStoreObj.AddToken(userId, accessToken, refreshToken)
}
}
func DeleteToken(userId string) {
func DeleteToken(userId, accessToken string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.DeleteToken(userId)
SessionStoreObj.RedisMemoryStoreObj.DeleteToken(userId, accessToken)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.DeleteToken(userId)
SessionStoreObj.InMemoryStoreObj.DeleteToken(userId, accessToken)
}
}
func GetToken(userId string) string {
func DeleteUserSession(userId string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
return SessionStoreObj.RedisMemoryStoreObj.GetToken(userId)
SessionStoreObj.RedisMemoryStoreObj.DeleteUserSession(userId)
}
if SessionStoreObj.InMemoryStoreObj != nil {
return SessionStoreObj.InMemoryStoreObj.GetToken(userId)
SessionStoreObj.InMemoryStoreObj.DeleteUserSession(userId)
}
}
func GetToken(userId, accessToken string) string {
if SessionStoreObj.RedisMemoryStoreObj != nil {
return SessionStoreObj.RedisMemoryStoreObj.GetToken(userId, accessToken)
}
if SessionStoreObj.InMemoryStoreObj != nil {
return SessionStoreObj.InMemoryStoreObj.GetToken(userId, accessToken)
}
return ""
@@ -53,6 +65,35 @@ func ClearStore() {
}
}
func SetSocailLoginState(key, state string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.SetSocialLoginState(key, state)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.SetSocialLoginState(key, state)
}
}
func GetSocailLoginState(key string) string {
if SessionStoreObj.RedisMemoryStoreObj != nil {
return SessionStoreObj.RedisMemoryStoreObj.GetSocialLoginState(key)
}
if SessionStoreObj.InMemoryStoreObj != nil {
return SessionStoreObj.InMemoryStoreObj.GetSocialLoginState(key)
}
return ""
}
func RemoveSocialLoginState(key string) {
if SessionStoreObj.RedisMemoryStoreObj != nil {
SessionStoreObj.RedisMemoryStoreObj.RemoveSocialLoginState(key)
}
if SessionStoreObj.InMemoryStoreObj != nil {
SessionStoreObj.InMemoryStoreObj.RemoveSocialLoginState(key)
}
}
func InitSession() {
if constants.REDIS_URL != "" {
log.Println("Using redis store to save sessions")
@@ -75,7 +116,8 @@ func InitSession() {
} else {
log.Println("Using in memory store to save sessions")
SessionStoreObj.InMemoryStoreObj = &InMemoryStore{
store: make(map[string]string),
store: map[string]map[string]string{},
socialLoginState: map[string]string{},
}
}
}

View File

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

View File

@@ -1,7 +1,6 @@
package utils
import (
"log"
"net/http"
"github.com/authorizerdev/authorizer/server/constants"
@@ -11,18 +10,25 @@ import (
func SetCookie(gc *gin.Context, token string) {
secure := true
httpOnly := true
host := GetHostName(constants.AUTHORIZER_URL)
log.Println("=> cookie host", host)
domain := GetDomainName(constants.AUTHORIZER_URL)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(constants.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
gc.SetCookie(constants.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly)
}
func GetCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(constants.COOKIE_NAME)
if err != nil {
cookie, err = gc.Request.Cookie(constants.COOKIE_NAME + "-client")
if err != nil {
return "", err
}
}
return cookie.Value, nil
}
@@ -31,11 +37,13 @@ func DeleteCookie(gc *gin.Context) {
secure := true
httpOnly := true
if !constants.IS_PROD {
secure = false
host := GetDomainName(constants.AUTHORIZER_URL)
domain := GetDomainName(constants.AUTHORIZER_URL)
if domain != "localhost" {
domain = "." + domain
}
host := GetHostName(constants.AUTHORIZER_URL)
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(constants.COOKIE_NAME, "", -1, "/", host, secure, httpOnly)
gc.SetCookie(constants.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly)
}

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;">
<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 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>
</td>
</tr>

View File

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

View File

@@ -0,0 +1,19 @@
package utils
import "net/http"
func GetIP(r *http.Request) string {
IPAddress := r.Header.Get("X-Real-Ip")
if IPAddress == "" {
IPAddress = r.Header.Get("X-Forwarded-For")
}
if IPAddress == "" {
IPAddress = r.RemoteAddr
}
return IPAddress
}
func GetUserAgent(r *http.Request) string {
return r.UserAgent()
}

View File

@@ -5,7 +5,7 @@ import (
"strings"
)
// function to get hostname
// GetHostName function to get hostname
func GetHostName(auth_url string) string {
u, err := url.Parse(auth_url)
if err != nil {
@@ -17,7 +17,7 @@ func GetHostName(auth_url string) string {
return host
}
// function to get domain name
// GetDomainName function to get domain name
func GetDomainName(auth_url string) string {
u, err := url.Parse(auth_url)
if err != nil {

25
server/utils/urls_test.go Normal file
View File

@@ -0,0 +1,25 @@
package utils
import "testing"
func TestGetHostName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com"
got := GetHostName(authorizer_url)
want := "test.herokuapp.com"
if got != want {
t.Errorf("GetHostName Test failed got %s, wanted %s", got, want)
}
}
func TestGetDomainName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com"
got := GetDomainName(authorizer_url)
want := "herokuapp.com"
if got != want {
t.Errorf("GetHostName Test failed got %q, wanted %q", got, want)
}
}

View File

@@ -10,6 +10,7 @@ import (
type UserInfo struct {
Email string `json:"email"`
Host string `json:"host"`
RedirectURL string `json:"redirect_url"`
}
type CustomClaim struct {
@@ -28,7 +29,7 @@ func CreateVerificationToken(email string, tokenType string) (string, error) {
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
},
tokenType,
UserInfo{Email: email, Host: constants.AUTHORIZER_URL},
UserInfo{Email: email, Host: constants.AUTHORIZER_URL, RedirectURL: constants.APP_URL},
}
return t.SignedString([]byte(constants.JWT_SECRET))