Merge pull request #87 from authorizerdev/feat/use-opend-id-standard-claims

feat/use opend id standard claims
This commit is contained in:
Lakhan Samani 2021-12-24 08:49:01 +05:30 committed by GitHub
commit e5761f1e42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
79 changed files with 2685 additions and 1197 deletions

View File

@ -2,8 +2,11 @@ ENV=production
DATABASE_URL=data.db DATABASE_URL=data.db
DATABASE_TYPE=sqlite DATABASE_TYPE=sqlite
ADMIN_SECRET=admin ADMIN_SECRET=admin
DISABLE_EMAIL_VERIFICATION=true
JWT_SECRET=random_string JWT_SECRET=random_string
SENDER_EMAIL=username
SENDER_PASSWORD=password
SMTP_HOST=smtp.mailtrap.io
SMTP_PORT=2525
JWT_TYPE=HS256 JWT_TYPE=HS256
ROLES=user ROLES=user
DEFAULT_ROLES=user DEFAULT_ROLES=user

View File

@ -6,4 +6,4 @@ cmd:
clean: clean:
rm -rf build rm -rf build
test: test:
cd server && go clean --testcache && go test -v ./... cd server && go clean --testcache && go test -v ./__test__

10
TODO.md
View File

@ -1,5 +1,15 @@
# Task List # Task List
## Open ID compatible claims and schema
- [x] Rename `schema.graphqls` and re generate schema
- [x] Rename to snake case [files + schema]
- [x] Refactor db models
- [x] Check extra data in oauth profile and save accordingly
- [x] Update all the resolver to make them compatible with schema changes
- [x] Update JWT claims
- [x] Write integration tests for all resolvers
## Feature Multiple sessions ## Feature Multiple sessions
- Multiple sessions for users to login use hMset from redis for this - Multiple sessions for users to login use hMset from redis for this

View File

@ -0,0 +1,27 @@
package test
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestCors(t *testing.T) {
allowedOrigin := "http://localhost:8080" // The allowed origin that you want to check
notAllowedOrigin := "http://myapp.com"
s := testSetup()
defer s.Server.Close()
client := &http.Client{}
req, _ := createContext(s)
req.Header.Add("Origin", allowedOrigin)
res, _ := client.Do(req)
// You should get your origin (or a * depending on your config) if the
// passed origin is allowed.
o := res.Header.Get("Access-Control-Allow-Origin")
assert.NotEqual(t, o, notAllowedOrigin, "Origins should not match")
assert.Equal(t, o, allowedOrigin, "Origins do match")
}

View File

@ -0,0 +1,34 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func deleteUserTest(s TestSetup, t *testing.T) {
t.Run(`should delete users with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "delete_user." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.DeleteUser(ctx, model.DeleteUserInput{
Email: email,
})
assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
_, err = resolvers.DeleteUser(ctx, model.DeleteUserInput{
Email: email,
})
assert.Nil(t, err)
cleanData(email)
})
}

View File

@ -1,23 +1,19 @@
package env package test
import ( import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestEnvs(t *testing.T) { func TestEnvs(t *testing.T) {
constants.ENV_PATH = "../../.env.sample" constants.ENV_PATH = "../../.env.sample"
InitEnv()
assert.Equal(t, constants.ADMIN_SECRET, "admin") assert.Equal(t, constants.ADMIN_SECRET, "admin")
assert.Equal(t, constants.ENV, "production") assert.Equal(t, constants.ENV, "production")
assert.Equal(t, constants.DATABASE_URL, "data.db") assert.False(t, constants.DISABLE_EMAIL_VERIFICATION)
assert.Equal(t, constants.DATABASE_TYPE, enum.Sqlite.String()) assert.False(t, constants.DISABLE_MAGIC_LINK_LOGIN)
assert.True(t, constants.DISABLE_EMAIL_VERIFICATION)
assert.True(t, constants.DISABLE_MAGIC_LOGIN)
assert.False(t, constants.DISABLE_BASIC_AUTHENTICATION) assert.False(t, constants.DISABLE_BASIC_AUTHENTICATION)
assert.Equal(t, constants.JWT_TYPE, "HS256") assert.Equal(t, constants.JWT_TYPE, "HS256")
assert.Equal(t, constants.JWT_SECRET, "random_string") assert.Equal(t, constants.JWT_SECRET, "random_string")

View File

@ -0,0 +1,35 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func forgotPasswordTest(s TestSetup, t *testing.T) {
t.Run(`should run forgot password`, func(t *testing.T) {
_, ctx := createContext(s)
email := "forgot_password." + s.TestInfo.Email
_, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err = resolvers.ForgotPassword(ctx, model.ForgotPasswordInput{
Email: email,
})
assert.Nil(t, err, "no errors for forgot password")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.ForgotPassword.String())
assert.Nil(t, err)
assert.Equal(t, verificationRequest.Identifier, enum.ForgotPassword.String())
cleanData(email)
})
}

View File

@ -0,0 +1,58 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func loginTests(s TestSetup, t *testing.T) {
t.Run(`should login`, func(t *testing.T) {
_, ctx := createContext(s)
email := "login." + s.TestInfo.Email
_, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err = resolvers.Login(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,
})
assert.NotNil(t, err, "should fail because email is not verified")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
_, err = resolvers.Login(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,
Roles: []string{"test"},
})
assert.NotNil(t, err, "invalid roles")
_, err = resolvers.Login(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password + "s",
})
assert.NotNil(t, err, "invalid password")
loginRes, err := resolvers.Login(ctx, model.LoginInput{
Email: email,
Password: s.TestInfo.Password,
})
assert.Nil(t, err, "login successful")
assert.NotNil(t, loginRes.AccessToken, "access token should not be empty")
cleanData(email)
})
}

View File

@ -0,0 +1,35 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func logoutTests(s TestSetup, t *testing.T) {
t.Run(`should logout user`, func(t *testing.T) {
req, ctx := createContext(s)
email := "logout." + s.TestInfo.Email
_, err := resolvers.MagicLinkLogin(ctx, model.MagicLinkLoginInput{
Email: email,
})
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.MagicLinkLogin.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
_, err = resolvers.Logout(ctx)
assert.Nil(t, err)
_, err = resolvers.Profile(ctx)
assert.NotNil(t, err, "unauthorized")
cleanData(email)
})
}

View File

@ -0,0 +1,35 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func magicLinkLoginTests(s TestSetup, t *testing.T) {
t.Run(`should login with magic link`, func(t *testing.T) {
req, ctx := createContext(s)
email := "magic_link_login." + s.TestInfo.Email
_, err := resolvers.MagicLinkLogin(ctx, model.MagicLinkLoginInput{
Email: email,
})
assert.Nil(t, err)
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.MagicLinkLogin.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
_, err = resolvers.Profile(ctx)
assert.Nil(t, err)
cleanData(email)
})
}

View File

@ -0,0 +1,23 @@
package test
import (
"context"
"testing"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func metaTests(s TestSetup, t *testing.T) {
t.Run(`should get meta information`, func(t *testing.T) {
ctx := context.Background()
meta, err := resolvers.Meta(ctx)
assert.Nil(t, err)
assert.False(t, meta.IsFacebookLoginEnabled)
assert.False(t, meta.IsGoogleLoginEnabled)
assert.False(t, meta.IsGithubLoginEnabled)
assert.True(t, meta.IsEmailVerificationEnabled)
assert.True(t, meta.IsBasicAuthenticationEnabled)
assert.True(t, meta.IsMagicLinkLoginEnabled)
})
}

View File

@ -0,0 +1,42 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func profileTests(s TestSetup, t *testing.T) {
t.Run(`should get profile only with token`, func(t *testing.T) {
req, ctx := createContext(s)
email := "profile." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.Profile(ctx)
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
profileRes, err := resolvers.Profile(ctx)
assert.Nil(t, err)
newEmail := *&profileRes.Email
assert.Equal(t, email, newEmail, "emails should be equal")
cleanData(email)
})
}

View File

@ -0,0 +1,31 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func resendVerifyEmailTests(s TestSetup, t *testing.T) {
t.Run(`should resend verification email`, func(t *testing.T) {
_, ctx := createContext(s)
email := "resend_verify_email." + s.TestInfo.Email
_, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err = resolvers.ResendVerifyEmail(ctx, model.ResendVerifyEmailInput{
Email: email,
Identifier: enum.BasicAuthSignup.String(),
})
assert.Nil(t, err)
cleanData(email)
})
}

View File

@ -0,0 +1,49 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func resetPasswordTest(s TestSetup, t *testing.T) {
t.Run(`should reset password`, func(t *testing.T) {
email := "reset_password." + s.TestInfo.Email
_, ctx := createContext(s)
_, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err = resolvers.ForgotPassword(ctx, model.ForgotPasswordInput{
Email: email,
})
assert.Nil(t, err, "no errors for forgot password")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.ForgotPassword.String())
assert.Nil(t, err, "should get forgot password request")
_, err = resolvers.ResetPassword(ctx, model.ResetPasswordInput{
Token: verificationRequest.Token,
Password: "test1",
ConfirmPassword: "test",
})
assert.NotNil(t, err, "passowrds don't match")
_, err = resolvers.ResetPassword(ctx, model.ResetPasswordInput{
Token: verificationRequest.Token,
Password: "test1",
ConfirmPassword: "test1",
})
assert.Nil(t, err, "password changed successfully")
cleanData(email)
})
}

View File

@ -0,0 +1,47 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
)
func TestResolvers(t *testing.T) {
databases := map[string]string{
enum.Sqlite.String(): "../../data.db",
enum.Arangodb.String(): "http://root:root@localhost:8529",
enum.Mongodb.String(): "mongodb://localhost:27017",
}
for dbType, dbURL := range databases {
constants.DATABASE_URL = dbURL
constants.DATABASE_TYPE = dbType
db.InitDB()
s := testSetup()
defer s.Server.Close()
t.Run("should pass tests for "+dbType, func(t *testing.T) {
loginTests(s, t)
signupTests(s, t)
forgotPasswordTest(s, t)
resendVerifyEmailTests(s, t)
resetPasswordTest(s, t)
verifyEmailTest(s, t)
sessionTests(s, t)
profileTests(s, t)
updateProfileTests(s, t)
magicLinkLoginTests(s, t)
logoutTests(s, t)
metaTests(s, t)
// admin tests
verificationRequestsTest(s, t)
usersTest(s, t)
deleteUserTest(s, t)
updateUserTest(s, t)
})
}
}

View File

@ -0,0 +1,42 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func sessionTests(s TestSetup, t *testing.T) {
t.Run(`should allow access to profile with session only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "session." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.Session(ctx, []string{})
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
sessionRes, err := resolvers.Session(ctx, []string{})
assert.Nil(t, err)
newToken := *sessionRes.AccessToken
assert.Equal(t, token, newToken, "tokens should be equal")
cleanData(email)
})
}

View File

@ -0,0 +1,47 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func signupTests(s TestSetup, t *testing.T) {
t.Run(`should complete the signup and check duplicates`, func(t *testing.T) {
_, ctx := createContext(s)
email := "signup." + s.TestInfo.Email
res, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password + "s",
})
assert.NotNil(t, err, "invalid password errors")
res, err = resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
user := *res.User
assert.Equal(t, email, user.Email)
assert.Nil(t, res.AccessToken, "access token should be nil")
res, err = resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
assert.NotNil(t, err, "should throw duplicate email error")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
assert.Nil(t, err)
assert.Equal(t, email, verificationRequest.Email)
cleanData(email)
})
}

93
server/__test__/test.go Normal file
View File

@ -0,0 +1,93 @@
package test
import (
"context"
"net/http"
"net/http/httptest"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/env"
"github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/authorizerdev/authorizer/server/session"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
// common user data to share across tests
type TestData struct {
Email string
Password string
}
type TestSetup struct {
GinEngine *gin.Engine
GinContext *gin.Context
Server *httptest.Server
TestInfo TestData
}
func cleanData(email string) {
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
if err == nil {
err = db.Mgr.DeleteVerificationRequest(verificationRequest)
}
verificationRequest, err = db.Mgr.GetVerificationByEmail(email, enum.ForgotPassword.String())
if err == nil {
err = db.Mgr.DeleteVerificationRequest(verificationRequest)
}
verificationRequest, err = db.Mgr.GetVerificationByEmail(email, enum.UpdateEmail.String())
if err == nil {
err = db.Mgr.DeleteVerificationRequest(verificationRequest)
}
dbUser, err := db.Mgr.GetUserByEmail(email)
if err == nil {
db.Mgr.DeleteUser(dbUser)
db.Mgr.DeleteUserSession(dbUser.ID)
}
}
func createContext(s TestSetup) (*http.Request, context.Context) {
req, _ := http.NewRequest(
"POST",
"http://"+s.Server.Listener.Addr().String()+"/graphql",
nil,
)
ctx := context.WithValue(req.Context(), "GinContextKey", s.GinContext)
s.GinContext.Request = req
return req, ctx
}
func testSetup() TestSetup {
testData := TestData{
Email: "authorizer_tester@yopmail.com",
Password: "test",
}
constants.ENV_PATH = "../../.env.sample"
env.InitEnv()
session.InitSession()
w := httptest.NewRecorder()
c, r := gin.CreateTestContext(w)
r.Use(location.Default())
r.Use(middlewares.GinContextToContextMiddleware())
r.Use(middlewares.CORSMiddleware())
r.POST("/graphql", handlers.GraphqlHandler())
server := httptest.NewServer(r)
return TestSetup{
GinEngine: r,
GinContext: c,
Server: server,
TestInfo: testData,
}
}

View File

@ -0,0 +1,53 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func updateProfileTests(s TestSetup, t *testing.T) {
t.Run(`should update the profile with access token only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "update_profile." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
fName := "samani"
_, err := resolvers.UpdateProfile(ctx, model.UpdateProfileInput{
FamilyName: &fName,
})
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
token := *verifyRes.AccessToken
req.Header.Add("Authorization", "Bearer "+token)
_, err = resolvers.UpdateProfile(ctx, model.UpdateProfileInput{
FamilyName: &fName,
})
assert.Nil(t, err)
newEmail := "new_" + email
_, err = resolvers.UpdateProfile(ctx, model.UpdateProfileInput{
Email: &newEmail,
})
assert.Nil(t, err)
_, err = resolvers.Profile(ctx)
assert.NotNil(t, err, "unauthorized")
cleanData(newEmail)
cleanData(email)
})
}

View File

@ -0,0 +1,40 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func updateUserTest(s TestSetup, t *testing.T) {
t.Run(`should update the user with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "update_user." + s.TestInfo.Email
signupRes, _ := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
user := *signupRes.User
adminRole := "admin"
userRole := "user"
newRoles := []*string{&adminRole, &userRole}
_, err := resolvers.UpdateUser(ctx, model.UpdateUserInput{
ID: user.ID,
Roles: newRoles,
})
assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
_, err = resolvers.UpdateUser(ctx, model.UpdateUserInput{
ID: user.ID,
Roles: newRoles,
})
assert.Nil(t, err)
cleanData(email)
})
}

View File

@ -1,15 +1,16 @@
package utils package test
import ( import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestGetHostName(t *testing.T) { func TestGetHostName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com:80" authorizer_url := "http://test.herokuapp.com:80"
host, port := GetHostParts(authorizer_url) host, port := utils.GetHostParts(authorizer_url)
expectedHost := "test.herokuapp.com" expectedHost := "test.herokuapp.com"
assert.Equal(t, host, expectedHost, "hostname should be equal") assert.Equal(t, host, expectedHost, "hostname should be equal")
@ -19,7 +20,7 @@ func TestGetHostName(t *testing.T) {
func TestGetDomainName(t *testing.T) { func TestGetDomainName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com" authorizer_url := "http://test.herokuapp.com"
got := GetDomainName(authorizer_url) got := utils.GetDomainName(authorizer_url)
want := "herokuapp.com" want := "herokuapp.com"
assert.Equal(t, got, want, "domain name should be equal") assert.Equal(t, got, want, "domain name should be equal")

View File

@ -0,0 +1,33 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func usersTest(s TestSetup, t *testing.T) {
t.Run(`should get users list with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "users." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
users, err := resolvers.Users(ctx)
assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
users, err = resolvers.Users(ctx)
assert.Nil(t, err)
rLen := len(users)
assert.GreaterOrEqual(t, rLen, 1)
cleanData(email)
})
}

View File

@ -0,0 +1,43 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func TestIsValidEmail(t *testing.T) {
validEmail := "lakhan@gmail.com"
invalidEmail1 := "lakhan"
invalidEmail2 := "lakhan.me"
assert.True(t, utils.IsValidEmail(validEmail), "it should be valid email")
assert.False(t, utils.IsValidEmail(invalidEmail1), "it should be invalid email")
assert.False(t, utils.IsValidEmail(invalidEmail2), "it should be invalid email")
}
func TestIsValidOrigin(t *testing.T) {
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
// as we trim them off while running the main function
constants.ALLOWED_ORIGINS = []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}
assert.False(t, utils.IsValidOrigin("http://myapp.com"), "it should be invalid origin")
assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin")
assert.True(t, utils.IsValidOrigin("http://app.google.com"), "it should be valid origin")
assert.False(t, utils.IsValidOrigin("http://app.google.ind"), "it should be invalid origin")
assert.True(t, utils.IsValidOrigin("http://app.google.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://xyx.abc.com"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://xyx.abc.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://xyxabc.in"), "it should be valid origin")
assert.True(t, utils.IsValidOrigin("http://localhost:8080"), "it should be valid origin")
}
func TestIsValidIdentifier(t *testing.T) {
assert.False(t, utils.IsValidVerificationIdentifier("test"), "it should be invalid identifier")
assert.True(t, utils.IsValidVerificationIdentifier(enum.BasicAuthSignup.String()), "it should be valid identifier")
assert.True(t, utils.IsValidVerificationIdentifier(enum.UpdateEmail.String()), "it should be valid identifier")
assert.True(t, utils.IsValidVerificationIdentifier(enum.ForgotPassword.String()), "it should be valid identifier")
}

View File

@ -0,0 +1,35 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func verificationRequestsTest(s TestSetup, t *testing.T) {
t.Run(`should get verification requests with admin secret only`, func(t *testing.T) {
req, ctx := createContext(s)
email := "verification_requests." + s.TestInfo.Email
resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
requests, err := resolvers.VerificationRequests(ctx)
assert.NotNil(t, err, "unauthorizer")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET)
requests, err = resolvers.VerificationRequests(ctx)
assert.Nil(t, err)
rLen := len(requests)
assert.GreaterOrEqual(t, rLen, 1)
cleanData(email)
})
}

View File

@ -0,0 +1,38 @@
package test
import (
"testing"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert"
)
func verifyEmailTest(s TestSetup, t *testing.T) {
t.Run(`should verify email`, func(t *testing.T) {
_, ctx := createContext(s)
email := "verify_email." + s.TestInfo.Email
res, err := resolvers.Signup(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
user := *res.User
assert.Equal(t, email, user.Email)
assert.Nil(t, res.AccessToken, "access token should be nil")
verificationRequest, err := db.Mgr.GetVerificationByEmail(email, enum.BasicAuthSignup.String())
assert.Nil(t, err)
assert.Equal(t, email, verificationRequest.Email)
verifyRes, err := resolvers.VerifyEmail(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
assert.Nil(t, err)
assert.NotEqual(t, verifyRes.AccessToken, "", "access token should not be empty")
cleanData(email)
})
}

View File

@ -1,5 +1,6 @@
package constants package constants
// this constants are configured via env
var ( var (
ADMIN_SECRET = "" ADMIN_SECRET = ""
ENV = "" ENV = ""
@ -17,14 +18,14 @@ var (
ALLOWED_ORIGINS = []string{} ALLOWED_ORIGINS = []string{}
AUTHORIZER_URL = "" AUTHORIZER_URL = ""
APP_URL = "" APP_URL = ""
PORT = "8080" PORT = ""
REDIS_URL = "" REDIS_URL = ""
IS_PROD = false IS_PROD = false
COOKIE_NAME = "" COOKIE_NAME = ""
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 DISABLE_MAGIC_LINK_LOGIN = false
// ROLES // ROLES
ROLES = []string{} ROLES = []string{}

View File

@ -36,16 +36,12 @@ func initArangodb() (arangoDriver.Database, error) {
if arangodb_exists { if arangodb_exists {
log.Println(constants.DATABASE_NAME + " db exists already") log.Println(constants.DATABASE_NAME + " db exists already")
arangodb, err = arangoClient.Database(nil, constants.DATABASE_NAME) arangodb, err = arangoClient.Database(nil, constants.DATABASE_NAME)
if err != nil { if err != nil {
return nil, err return nil, err
} }
} else { } else {
arangodb, err = arangoClient.CreateDatabase(nil, constants.DATABASE_NAME, nil) arangodb, err = arangoClient.CreateDatabase(nil, constants.DATABASE_NAME, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -61,11 +57,11 @@ func initArangodb() (arangoDriver.Database, error) {
} }
} }
userCollection, _ := arangodb.Collection(nil, Collections.User) userCollection, _ := arangodb.Collection(nil, Collections.User)
userCollection.EnsureHashIndex(ctx, []string{"id"}, &arangoDriver.EnsureHashIndexOptions{ userCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true, Unique: true,
Sparse: true, Sparse: true,
}) })
userCollection.EnsureHashIndex(ctx, []string{"email"}, &arangoDriver.EnsureHashIndexOptions{ userCollection.EnsureHashIndex(ctx, []string{"phone_number"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true, Unique: true,
Sparse: true, Sparse: true,
}) })
@ -79,11 +75,8 @@ func initArangodb() (arangoDriver.Database, error) {
log.Println("error creating collection("+Collections.VerificationRequest+"):", err) log.Println("error creating collection("+Collections.VerificationRequest+"):", err)
} }
} }
verificationRequestCollection, _ := arangodb.Collection(nil, Collections.VerificationRequest) verificationRequestCollection, _ := arangodb.Collection(nil, Collections.VerificationRequest)
verificationRequestCollection.EnsureHashIndex(ctx, []string{"id"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true,
})
verificationRequestCollection.EnsureHashIndex(ctx, []string{"email", "identifier"}, &arangoDriver.EnsureHashIndexOptions{ verificationRequestCollection.EnsureHashIndex(ctx, []string{"email", "identifier"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true, Unique: true,
Sparse: true, Sparse: true,
@ -103,8 +96,7 @@ func initArangodb() (arangoDriver.Database, error) {
} }
sessionCollection, _ := arangodb.Collection(nil, Collections.Session) sessionCollection, _ := arangodb.Collection(nil, Collections.Session)
sessionCollection.EnsureHashIndex(ctx, []string{"id"}, &arangoDriver.EnsureHashIndexOptions{ sessionCollection.EnsureHashIndex(ctx, []string{"user_id"}, &arangoDriver.EnsureHashIndexOptions{
Unique: true,
Sparse: true, Sparse: true,
}) })

View File

@ -26,8 +26,9 @@ type Manager interface {
GetVerificationByToken(token string) (VerificationRequest, error) GetVerificationByToken(token string) (VerificationRequest, error)
DeleteVerificationRequest(verificationRequest VerificationRequest) error DeleteVerificationRequest(verificationRequest VerificationRequest) error
GetVerificationRequests() ([]VerificationRequest, error) GetVerificationRequests() ([]VerificationRequest, error)
GetVerificationByEmail(email string) (VerificationRequest, error) GetVerificationByEmail(email string, identifier string) (VerificationRequest, error)
AddSession(session Session) error AddSession(session Session) error
DeleteUserSession(userId string) error
} }
type manager struct { type manager struct {
@ -94,8 +95,8 @@ func InitDB() {
Mgr = &manager{ Mgr = &manager{
sqlDB: nil, sqlDB: nil,
mongodb: nil,
arangodb: arangodb, arangodb: arangodb,
mongodb: nil,
} }
break break

View File

@ -36,25 +36,21 @@ func initMongodb() (*mongo.Database, error) {
userCollection := mongodb.Collection(Collections.User, options.Collection()) userCollection := mongodb.Collection(Collections.User, options.Collection())
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{ mongo.IndexModel{
Keys: bson.M{"id": 1}, Keys: bson.M{"email": 1},
Options: options.Index().SetUnique(true).SetSparse(true), Options: options.Index().SetUnique(true).SetSparse(true),
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())
userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ userCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{ mongo.IndexModel{
Keys: bson.M{"email": 1}, Keys: bson.M{"phone_number": 1},
Options: options.Index().SetUnique(true).SetSparse(true), Options: options.Index().SetUnique(true).SetSparse(true).SetPartialFilterExpression(map[string]interface{}{
"phone_number": map[string]string{"$type": "string"},
}),
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())
mongodb.CreateCollection(ctx, Collections.VerificationRequest, options.CreateCollection()) mongodb.CreateCollection(ctx, Collections.VerificationRequest, options.CreateCollection())
verificationRequestCollection := mongodb.Collection(Collections.VerificationRequest, options.Collection()) verificationRequestCollection := mongodb.Collection(Collections.VerificationRequest, options.Collection())
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{
Keys: bson.M{"id": 1},
Options: options.Index().SetUnique(true).SetSparse(true),
},
}, options.CreateIndexes())
verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ verificationRequestCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{ mongo.IndexModel{
Keys: bson.M{"email": 1, "identifier": 1}, Keys: bson.M{"email": 1, "identifier": 1},
@ -72,8 +68,8 @@ func initMongodb() (*mongo.Database, error) {
sessionCollection := mongodb.Collection(Collections.Session, options.Collection()) sessionCollection := mongodb.Collection(Collections.Session, options.Collection())
sessionCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{ sessionCollection.Indexes().CreateMany(ctx, []mongo.IndexModel{
mongo.IndexModel{ mongo.IndexModel{
Keys: bson.M{"id": 1}, Keys: bson.M{"user_id": 1},
Options: options.Index().SetUnique(true).SetSparse(true), Options: options.Index().SetSparse(true),
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())

View File

@ -1,19 +1,20 @@
package db package db
import ( import (
"fmt"
"log" "log"
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
type Session struct { type Session struct {
Key string `json:"_key,omitempty" bson:"_key,omitempty"` // for arangodb Key string `json:"_key,omitempty" bson:"_key,omitempty"` // for arangodb
ObjectID string `json:"_id,omitempty" bson:"_id"` // for arangodb & mongodb ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
ID string `gorm:"primaryKey;type:char(36)" json:"id" bson:"id"` UserID string `gorm:"type:char(36),index:" json:"user_id" bson:"user_id"`
UserID string `gorm:"type:char(36)" json:"user_id" bson:"user_id"`
User User `json:"-" bson:"-"` User User `json:"-" bson:"-"`
UserAgent string `json:"user_agent" bson:"user_agent"` UserAgent string `json:"user_agent" bson:"user_agent"`
IP string `json:"ip" bson:"ip"` IP string `json:"ip" bson:"ip"`
@ -29,7 +30,6 @@ func (mgr *manager) AddSession(session Session) error {
if IsORMSupported { if IsORMSupported {
session.Key = session.ID session.Key = session.ID
session.ObjectID = session.ID
res := mgr.sqlDB.Clauses( res := mgr.sqlDB.Clauses(
clause.OnConflict{ clause.OnConflict{
DoNothing: true, DoNothing: true,
@ -53,7 +53,6 @@ func (mgr *manager) AddSession(session Session) error {
if IsMongoDB { if IsMongoDB {
session.Key = session.ID session.Key = session.ID
session.ObjectID = session.ID
session.CreatedAt = time.Now().Unix() session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix() session.UpdatedAt = time.Now().Unix()
sessionCollection := mgr.mongodb.Collection(Collections.Session, options.Collection()) sessionCollection := mgr.mongodb.Collection(Collections.Session, options.Collection())
@ -66,3 +65,38 @@ func (mgr *manager) AddSession(session Session) error {
return nil return nil
} }
func (mgr *manager) DeleteUserSession(userId string) error {
if IsORMSupported {
result := mgr.sqlDB.Where("user_id = ?", userId).Delete(&Session{})
if result.Error != nil {
log.Println(`error deleting session:`, result.Error)
return result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf(`FOR d IN %s FILTER d.user_id == @userId REMOVE { _key: d._key } IN %s`, Collections.Session, Collections.Session)
bindVars := map[string]interface{}{
"userId": userId,
}
cursor, err := mgr.arangodb.Query(nil, query, bindVars)
if err != nil {
log.Println("=> error deleting arangodb session:", err)
return err
}
defer cursor.Close()
}
if IsMongoDB {
sessionCollection := mgr.mongodb.Collection(Collections.Session, options.Collection())
_, err := sessionCollection.DeleteMany(nil, bson.M{"user_id": userId}, options.Delete())
if err != nil {
log.Println("error deleting session:", err)
return err
}
}
return nil
}

View File

@ -7,6 +7,7 @@ import (
"github.com/arangodb/go-driver" "github.com/arangodb/go-driver"
arangoDriver "github.com/arangodb/go-driver" arangoDriver "github.com/arangodb/go-driver"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/google/uuid" "github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson" "go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
@ -15,18 +16,24 @@ import (
type User struct { type User struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ObjectID string `json:"_id,omitempty" bson:"_id"` // for arangodb & mongodb ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
ID string `gorm:"primaryKey;type:char(36)" json:"id" bson:"id"`
FirstName string `json:"first_name" bson:"first_name"`
LastName string `json:"last_name" bson:"last_name"`
Email string `gorm:"unique" json:"email" bson:"email"` Email string `gorm:"unique" json:"email" bson:"email"`
Password string `gorm:"type:text" json:"password" bson:"password"` EmailVerifiedAt *int64 `json:"email_verified_at" bson:"email_verified_at"`
SignupMethod string `json:"signup_method" bson:"signup_method"` Password *string `gorm:"type:text" json:"password" bson:"password"`
EmailVerifiedAt int64 `json:"email_verified_at" bson:"email_verified_at"` SignupMethods string `json:"signup_methods" bson:"signup_methods"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"` GivenName *string `json:"given_name" bson:"given_name"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"` FamilyName *string `json:"family_name" bson:"family_name"`
Image string `gorm:"type:text" json:"image" bson:"image"` MiddleName *string `json:"middle_name" bson:"middle_name"`
Nickname *string `json:"nickname" bson:"nickname"`
Gender *string `json:"gender" bson:"gender"`
Birthdate *string `json:"birthdate" bson:"birthdate"`
PhoneNumber *string `gorm:"unique" json:"phone_number" bson:"phone_number"`
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
Roles string `json:"roles" bson:"roles"` Roles string `json:"roles" bson:"roles"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
} }
// AddUser function to add user even with email conflict // AddUser function to add user even with email conflict
@ -35,10 +42,13 @@ func (mgr *manager) AddUser(user User) (User, error) {
user.ID = uuid.New().String() user.ID = uuid.New().String()
} }
if user.Roles == "" {
user.Roles = constants.DEFAULT_ROLES[0]
}
if IsORMSupported { if IsORMSupported {
// copy id as value for fields required for mongodb & arangodb // copy id as value for fields required for mongodb & arangodb
user.Key = user.ID user.Key = user.ID
user.ObjectID = user.ID
result := mgr.sqlDB.Clauses( result := mgr.sqlDB.Clauses(
clause.OnConflict{ clause.OnConflict{
UpdateAll: true, UpdateAll: true,
@ -61,14 +71,13 @@ func (mgr *manager) AddUser(user User) (User, error) {
return user, err return user, err
} }
user.Key = meta.Key user.Key = meta.Key
user.ObjectID = meta.ID.String() user.ID = meta.ID.String()
} }
if IsMongoDB { if IsMongoDB {
user.CreatedAt = time.Now().Unix() user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix() user.UpdatedAt = time.Now().Unix()
user.Key = user.ID user.Key = user.ID
user.ObjectID = user.ID
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection()) userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
_, err := userCollection.InsertOne(nil, user) _, err := userCollection.InsertOne(nil, user)
if err != nil { if err != nil {
@ -102,12 +111,12 @@ func (mgr *manager) UpdateUser(user User) (User, error) {
} }
user.Key = meta.Key user.Key = meta.Key
user.ObjectID = meta.ID.String() user.ID = meta.ID.String()
} }
if IsMongoDB { if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection()) userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
_, err := userCollection.UpdateOne(nil, bson.M{"id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions()) _, err := userCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": user.ID}}, bson.M{"$set": user}, options.MergeUpdateOptions())
if err != nil { if err != nil {
log.Println("error updating user:", err) log.Println("error updating user:", err)
return user, err return user, err
@ -236,7 +245,7 @@ func (mgr *manager) GetUserByID(id string) (User, error) {
} }
if IsArangoDB { if IsArangoDB {
query := fmt.Sprintf("FOR d in %s FILTER d.id == @id LIMIT 1 RETURN d", Collections.User) query := fmt.Sprintf("FOR d in %s FILTER d._id == @id LIMIT 1 RETURN d", Collections.User)
bindVars := map[string]interface{}{ bindVars := map[string]interface{}{
"id": id, "id": id,
} }
@ -263,7 +272,7 @@ func (mgr *manager) GetUserByID(id string) (User, error) {
if IsMongoDB { if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection()) userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
err := userCollection.FindOne(nil, bson.M{"id": id}).Decode(&user) err := userCollection.FindOne(nil, bson.M{"_id": id}).Decode(&user)
if err != nil { if err != nil {
return user, err return user, err
} }
@ -293,7 +302,7 @@ func (mgr *manager) DeleteUser(user User) error {
if IsMongoDB { if IsMongoDB {
userCollection := mgr.mongodb.Collection(Collections.User, options.Collection()) userCollection := mgr.mongodb.Collection(Collections.User, options.Collection())
_, err := userCollection.DeleteOne(nil, bson.M{"id": user.ID}, options.Delete()) _, err := userCollection.DeleteOne(nil, bson.M{"_id": user.ID}, options.Delete())
if err != nil { if err != nil {
log.Println("error deleting user:", err) log.Println("error deleting user:", err)
return err return err

View File

@ -14,8 +14,7 @@ import (
type VerificationRequest struct { type VerificationRequest struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ObjectID string `json:"_id,omitempty" bson:"_id"` // for arangodb & mongodb ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
ID string `gorm:"primaryKey;type:char(36)" json:"id" bson:"id"`
Token string `gorm:"type:text" json:"token" bson:"token"` Token string `gorm:"type:text" json:"token" bson:"token"`
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"` Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at"` ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
@ -32,7 +31,6 @@ func (mgr *manager) AddVerification(verification VerificationRequest) (Verificat
if IsORMSupported { if IsORMSupported {
// copy id as value for fields required for mongodb & arangodb // copy id as value for fields required for mongodb & arangodb
verification.Key = verification.ID verification.Key = verification.ID
verification.ObjectID = verification.ID
result := mgr.sqlDB.Clauses(clause.OnConflict{ result := mgr.sqlDB.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}}, Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}), DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}),
@ -54,14 +52,13 @@ func (mgr *manager) AddVerification(verification VerificationRequest) (Verificat
return verification, err return verification, err
} }
verification.Key = meta.Key verification.Key = meta.Key
verification.ObjectID = meta.ID.String() verification.ID = meta.ID.String()
} }
if IsMongoDB { if IsMongoDB {
verification.CreatedAt = time.Now().Unix() verification.CreatedAt = time.Now().Unix()
verification.UpdatedAt = time.Now().Unix() verification.UpdatedAt = time.Now().Unix()
verification.Key = verification.ID verification.Key = verification.ID
verification.ObjectID = verification.ID
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection()) verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.InsertOne(nil, verification) _, err := verificationRequestCollection.InsertOne(nil, verification)
if err != nil { if err != nil {
@ -182,10 +179,10 @@ func (mgr *manager) GetVerificationByToken(token string) (VerificationRequest, e
return verification, nil return verification, nil
} }
func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, error) { func (mgr *manager) GetVerificationByEmail(email string, identifier string) (VerificationRequest, error) {
var verification VerificationRequest var verification VerificationRequest
if IsORMSupported { if IsORMSupported {
result := mgr.sqlDB.Where("email = ?", email).First(&verification) result := mgr.sqlDB.Where("email = ? AND identifier = ?", email, identifier).First(&verification)
if result.Error != nil { if result.Error != nil {
log.Println(`error getting verification token:`, result.Error) log.Println(`error getting verification token:`, result.Error)
@ -194,9 +191,10 @@ func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, e
} }
if IsArangoDB { if IsArangoDB {
query := fmt.Sprintf("FOR d in %s FILTER d.email == @email LIMIT 1 RETURN d", Collections.VerificationRequest) query := fmt.Sprintf("FOR d in %s FILTER d.email == @email FILTER d.identifier == @identifier LIMIT 1 RETURN d", Collections.VerificationRequest)
bindVars := map[string]interface{}{ bindVars := map[string]interface{}{
"email": email, "email": email,
"identifier": identifier,
} }
cursor, err := mgr.arangodb.Query(nil, query, bindVars) cursor, err := mgr.arangodb.Query(nil, query, bindVars)
@ -221,7 +219,7 @@ func (mgr *manager) GetVerificationByEmail(email string) (VerificationRequest, e
if IsMongoDB { if IsMongoDB {
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection()) verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
err := verificationRequestCollection.FindOne(nil, bson.M{"email": email}).Decode(&verification) err := verificationRequestCollection.FindOne(nil, bson.M{"email": email, "identifier": identifier}).Decode(&verification)
if err != nil { if err != nil {
return verification, err return verification, err
} }
@ -251,7 +249,7 @@ func (mgr *manager) DeleteVerificationRequest(verificationRequest VerificationRe
if IsMongoDB { if IsMongoDB {
verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection()) verificationRequestCollection := mgr.mongodb.Collection(Collections.VerificationRequest, options.Collection())
_, err := verificationRequestCollection.DeleteOne(nil, bson.M{"id": verificationRequest.ID}, options.Delete()) _, err := verificationRequestCollection.DeleteOne(nil, bson.M{"_id": verificationRequest.ID}, options.Delete())
if err != nil { if err != nil {
log.Println("error deleting verification request::", err) log.Println("error deleting verification request::", err)
return err return err

View File

@ -4,7 +4,7 @@ type SignupMethod int
const ( const (
BasicAuth SignupMethod = iota BasicAuth SignupMethod = iota
MagicLink MagicLinkLogin
Google Google
Github Github
Facebook Facebook
@ -13,7 +13,7 @@ const (
func (d SignupMethod) String() string { func (d SignupMethod) String() string {
return [...]string{ return [...]string{
"basic_auth", "basic_auth",
"magic_link", "magic_link_login",
"google", "google",
"github", "github",
"facebook", "facebook",

201
server/env/env.go vendored
View File

@ -1,7 +1,6 @@
package env package env
import ( import (
"flag"
"log" "log"
"os" "os"
"strings" "strings"
@ -25,13 +24,8 @@ func InitEnv() {
if constants.ENV_PATH == "" { if constants.ENV_PATH == "" {
constants.ENV_PATH = `.env` constants.ENV_PATH = `.env`
} }
ARG_DB_URL = flag.String("database_url", "", "Database connection string")
ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite")
ARG_AUTHORIZER_URL = flag.String("authorizer_url", "", "URL for authorizer instance, eg: https://xyz.herokuapp.com")
ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
flag.Parse() if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" {
if *ARG_ENV_FILE != "" {
constants.ENV_PATH = *ARG_ENV_FILE constants.ENV_PATH = *ARG_ENV_FILE
} }
@ -41,39 +35,16 @@ func InitEnv() {
} }
constants.VERSION = VERSION constants.VERSION = VERSION
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
constants.ENV = os.Getenv("ENV")
constants.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
constants.DATABASE_URL = os.Getenv("DATABASE_URL")
constants.DATABASE_NAME = os.Getenv("DATABASE_NAME")
constants.SMTP_HOST = os.Getenv("SMTP_HOST")
constants.SMTP_PORT = os.Getenv("SMTP_PORT")
constants.SENDER_EMAIL = os.Getenv("SENDER_EMAIL")
constants.SENDER_PASSWORD = os.Getenv("SENDER_PASSWORD")
constants.JWT_SECRET = os.Getenv("JWT_SECRET")
constants.JWT_TYPE = os.Getenv("JWT_TYPE")
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
constants.PORT = os.Getenv("PORT")
constants.REDIS_URL = os.Getenv("REDIS_URL")
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME")
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
constants.TWITTER_CLIENT_ID = os.Getenv("TWITTER_CLIENT_ID")
constants.TWITTER_CLIENT_SECRET = os.Getenv("TWITTER_CLIENT_SECRET")
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
constants.DISABLE_MAGIC_LOGIN = os.Getenv("DISABLE_MAGIC_LOGIN") == "true"
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
if constants.ADMIN_SECRET == "" {
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
if constants.ADMIN_SECRET == "" { if constants.ADMIN_SECRET == "" {
panic("root admin secret is required") panic("root admin secret is required")
} }
}
if constants.ENV == "" {
constants.ENV = os.Getenv("ENV")
if constants.ENV == "" { if constants.ENV == "" {
constants.ENV = "production" constants.ENV = "production"
} }
@ -84,6 +55,131 @@ func InitEnv() {
} else { } else {
constants.IS_PROD = false constants.IS_PROD = false
} }
}
if constants.DATABASE_TYPE == "" {
constants.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
log.Println(constants.DATABASE_TYPE)
if ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" {
constants.DATABASE_TYPE = *ARG_DB_TYPE
}
if constants.DATABASE_TYPE == "" {
panic("DATABASE_TYPE is required")
}
}
if constants.DATABASE_URL == "" {
constants.DATABASE_URL = os.Getenv("DATABASE_URL")
if ARG_DB_URL != nil && *ARG_DB_URL != "" {
constants.DATABASE_URL = *ARG_DB_URL
}
if constants.DATABASE_URL == "" {
panic("DATABASE_URL is required")
}
}
if constants.DATABASE_NAME == "" {
constants.DATABASE_NAME = os.Getenv("DATABASE_NAME")
if constants.DATABASE_NAME == "" {
constants.DATABASE_NAME = "authorizer"
}
}
if constants.SMTP_HOST == "" {
constants.SMTP_HOST = os.Getenv("SMTP_HOST")
}
if constants.SMTP_PORT == "" {
constants.SMTP_PORT = os.Getenv("SMTP_PORT")
}
if constants.SENDER_EMAIL == "" {
constants.SENDER_EMAIL = os.Getenv("SENDER_EMAIL")
}
if constants.SENDER_PASSWORD == "" {
constants.SENDER_PASSWORD = os.Getenv("SENDER_PASSWORD")
}
if constants.JWT_SECRET == "" {
constants.JWT_SECRET = os.Getenv("JWT_SECRET")
}
if constants.JWT_TYPE == "" {
constants.JWT_TYPE = os.Getenv("JWT_TYPE")
}
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = "role"
}
}
if constants.AUTHORIZER_URL == "" {
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
if ARG_AUTHORIZER_URL != nil && *ARG_AUTHORIZER_URL != "" {
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
}
}
if constants.PORT == "" {
constants.PORT = os.Getenv("PORT")
if constants.PORT == "" {
constants.PORT = "8080"
}
}
if constants.REDIS_URL == "" {
constants.REDIS_URL = os.Getenv("REDIS_URL")
}
if constants.COOKIE_NAME == "" {
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME")
}
if constants.GOOGLE_CLIENT_ID == "" {
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
}
if constants.GOOGLE_CLIENT_SECRET == "" {
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
}
if constants.GITHUB_CLIENT_ID == "" {
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
}
if constants.GITHUB_CLIENT_SECRET == "" {
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
}
if constants.FACEBOOK_CLIENT_ID == "" {
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
}
if constants.FACEBOOK_CLIENT_SECRET == "" {
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
}
if constants.RESET_PASSWORD_URL == "" {
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
}
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
constants.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
if constants.SMTP_HOST == "" || constants.SENDER_EMAIL == "" || constants.SENDER_PASSWORD == "" {
constants.DISABLE_EMAIL_VERIFICATION = true
constants.DISABLE_MAGIC_LINK_LOGIN = true
}
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",") allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
allowedOrigins := []string{} allowedOrigins := []string{}
@ -113,30 +209,6 @@ func InitEnv() {
constants.ALLOWED_ORIGINS = allowedOrigins constants.ALLOWED_ORIGINS = allowedOrigins
if *ARG_AUTHORIZER_URL != "" {
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
}
if *ARG_DB_URL != "" {
constants.DATABASE_URL = *ARG_DB_URL
}
if *ARG_DB_TYPE != "" {
constants.DATABASE_TYPE = *ARG_DB_TYPE
}
if constants.DATABASE_URL == "" {
panic("Database url is required")
}
if constants.DATABASE_TYPE == "" {
panic("Database type is required")
}
if constants.DATABASE_NAME == "" {
constants.DATABASE_NAME = "authorizer"
}
if constants.JWT_TYPE == "" { if constants.JWT_TYPE == "" {
constants.JWT_TYPE = "HS256" constants.JWT_TYPE = "HS256"
} }
@ -145,13 +217,8 @@ func InitEnv() {
constants.COOKIE_NAME = "authorizer" constants.COOKIE_NAME = "authorizer"
} }
if constants.SMTP_HOST == "" || constants.SENDER_EMAIL == "" || constants.SENDER_PASSWORD == "" {
constants.DISABLE_EMAIL_VERIFICATION = true
constants.DISABLE_MAGIC_LOGIN = true
}
if constants.DISABLE_EMAIL_VERIFICATION { if constants.DISABLE_EMAIL_VERIFICATION {
constants.DISABLE_MAGIC_LOGIN = true constants.DISABLE_MAGIC_LINK_LOGIN = true
} }
rolesSplit := strings.Split(os.Getenv("ROLES"), ",") rolesSplit := strings.Split(os.Getenv("ROLES"), ",")
@ -196,10 +263,6 @@ func InitEnv() {
constants.DEFAULT_ROLES = defaultRoles constants.DEFAULT_ROLES = defaultRoles
constants.PROTECTED_ROLES = protectedRoles constants.PROTECTED_ROLES = protectedRoles
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = "role"
}
if os.Getenv("ORGANIZATION_NAME") != "" { if os.Getenv("ORGANIZATION_NAME") != "" {
constants.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME") constants.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME")
} }

View File

@ -3,7 +3,7 @@ module github.com/authorizerdev/authorizer/server
go 1.16 go 1.16
require ( require (
github.com/99designs/gqlgen v0.13.0 github.com/99designs/gqlgen v0.14.0
github.com/arangodb/go-driver v1.2.1 github.com/arangodb/go-driver v1.2.1
github.com/coreos/go-oidc/v3 v3.1.0 github.com/coreos/go-oidc/v3 v3.1.0
github.com/gin-contrib/location v0.0.2 github.com/gin-contrib/location v0.0.2
@ -20,10 +20,10 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.1 // indirect github.com/modern-go/reflect2 v1.0.1 // indirect
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f
github.com/stretchr/testify v1.7.0 // indirect github.com/stretchr/testify v1.7.0
github.com/ugorji/go v1.2.6 // indirect github.com/ugorji/go v1.2.6 // indirect
github.com/vektah/gqlparser/v2 v2.1.0 github.com/vektah/gqlparser/v2 v2.2.0
go.mongodb.org/mongo-driver v1.8.1 // indirect go.mongodb.org/mongo-driver v1.8.1
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519
golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect golang.org/x/net v0.0.0-20210614182718-04defd469f4e // indirect
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914

View File

@ -31,15 +31,15 @@ cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohl
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/99designs/gqlgen v0.13.0 h1:haLTcUp3Vwp80xMVEg5KRNwzfUrgFdRmtBY8fuB8scA= github.com/99designs/gqlgen v0.14.0 h1:Wg8aNYQUjMR/4v+W3xD+7SizOy6lSvVeQ06AobNQAXI=
github.com/99designs/gqlgen v0.13.0/go.mod h1:NV130r6f4tpRWuAI+zsrSdooO/eWUv+Gyyoi3rEfXIk= github.com/99designs/gqlgen v0.14.0/go.mod h1:S7z4boV+Nx4VvzMUpVrY/YuHjFX4n7rDyuTqvAkuoRE=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM= github.com/agnivade/levenshtein v1.0.1/go.mod h1:CURSv5d9Uaml+FovSIICkLbAUZ9S4RqaHDIsdSBg7lM=
github.com/agnivade/levenshtein v1.0.3 h1:M5ZnqLOoZR8ygVq0FfkXsNOKzMCk0xRiow0R5+5VkQ0= github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
github.com/agnivade/levenshtein v1.0.3/go.mod h1:4SFRZbbXWLF4MU1T9Qg0pGgH3Pjs+t6ie5efyrwRJXs= github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/arangodb/go-driver v1.2.1 h1:HREDHhDmzdIWxHmfkfTESbYUnRjESjPh4WUuXq7FZa8= github.com/arangodb/go-driver v1.2.1 h1:HREDHhDmzdIWxHmfkfTESbYUnRjESjPh4WUuXq7FZa8=
@ -73,8 +73,8 @@ github.com/denisenkom/go-mssqldb v0.11.0 h1:9rHa233rhdOyrz2GcP9NM+gi2psgJZ4GWDpL
github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c h1:TUuUh0Xgj97tLMNtWtNvI9mIV6isjEb9lBMNv+77IGM= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48 h1:fRzb/w+pyskVMQ+UbP35JkH8yB7MYb4q/qhBarqZE6g=
github.com/dgryski/trifles v0.0.0-20190318185328-a8d75aae118c/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -89,7 +89,6 @@ github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA= github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/go-chi/chi v3.3.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
@ -113,7 +112,7 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v1.0.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY=
@ -246,6 +245,7 @@ github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMW
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc= github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
@ -328,7 +328,6 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.1/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -338,6 +337,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E= github.com/ugorji/go v1.2.6 h1:tGiWC9HENWE2tqYycIqFTNorMmFRVhNwCpDOpWqnk8E=
@ -347,8 +347,8 @@ github.com/ugorji/go/codec v1.2.6 h1:7kbGefxLoDBuYXOms4yD7223OpNMMPNPZxXk5TvFcyQ
github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw= github.com/ugorji/go/codec v1.2.6/go.mod h1:V6TCNZ4PHqoHGFZuSG1W8nrCzzdgA2DozYxWFFpvxTw=
github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= github.com/urfave/cli/v2 v2.1.1/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U= github.com/vektah/dataloaden v0.2.1-0.20190515034641-a19b9a6e7c9e/go.mod h1:/HUdMve7rvxZma+2ZELQeNh88+003LL7Pf/CZ089j8U=
github.com/vektah/gqlparser/v2 v2.1.0 h1:uiKJ+T5HMGGQM2kRKQ8Pxw8+Zq9qhhZhz/lieYvCMns= github.com/vektah/gqlparser/v2 v2.2.0 h1:bAc3slekAAJW6sZTi07aGq0OrfaCjj4jxARAaC7g2EM=
github.com/vektah/gqlparser/v2 v2.1.0/go.mod h1:SyUiHgLATUR8BiYURfTirrTcGpcE+4XkV2se04Px1Ms= github.com/vektah/gqlparser/v2 v2.2.0/go.mod h1:i3mQIGIrbK2PD1RrCeMTlVbkF2FJ6WkU1KJlJlC+3F4=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w= github.com/xdg-go/scram v1.0.2 h1:akYIkZ28e6A96dkWNJQu3nmCzH3YfwMPQExUYDaRv7w=
@ -565,7 +565,6 @@ golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200114235610-7ae403b6b589/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
@ -581,10 +580,12 @@ golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -715,5 +716,5 @@ honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sourcegraph.com/sourcegraph/appdash v0.0.0-20180110180208-2cc67fd64755/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU=
sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k= sourcegraph.com/sourcegraph/appdash-data v0.0.0-20151005221446-73f23eafcf67/go.mod h1:L5q+DGLGOQFpo1snNEkLOJT2d1YTW66rWNzatr3He1k=

File diff suppressed because it is too large Load Diff

View File

@ -2,19 +2,10 @@
package model package model
type AdminUpdateUserInput struct {
ID string `json:"id"`
Email *string `json:"email"`
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
Image *string `json:"image"`
Roles []*string `json:"roles"`
}
type AuthResponse struct { type AuthResponse struct {
Message string `json:"message"` Message string `json:"message"`
AccessToken *string `json:"accessToken"` AccessToken *string `json:"access_token"`
AccessTokenExpiresAt *int64 `json:"accessTokenExpiresAt"` ExpiresAt *int64 `json:"expires_at"`
User *User `json:"user"` User *User `json:"user"`
} }
@ -37,30 +28,30 @@ type LoginInput struct {
Roles []string `json:"roles"` Roles []string `json:"roles"`
} }
type MagicLoginInput struct { type MagicLinkLoginInput struct {
Email string `json:"email"` Email string `json:"email"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
} }
type Meta struct { type Meta struct {
Version string `json:"version"` Version string `json:"version"`
IsGoogleLoginEnabled bool `json:"isGoogleLoginEnabled"` IsGoogleLoginEnabled bool `json:"is_google_login_enabled"`
IsFacebookLoginEnabled bool `json:"isFacebookLoginEnabled"` IsFacebookLoginEnabled bool `json:"is_facebook_login_enabled"`
IsTwitterLoginEnabled bool `json:"isTwitterLoginEnabled"` IsGithubLoginEnabled bool `json:"is_github_login_enabled"`
IsGithubLoginEnabled bool `json:"isGithubLoginEnabled"` IsEmailVerificationEnabled bool `json:"is_email_verification_enabled"`
IsEmailVerificationEnabled bool `json:"isEmailVerificationEnabled"` IsBasicAuthenticationEnabled bool `json:"is_basic_authentication_enabled"`
IsBasicAuthenticationEnabled bool `json:"isBasicAuthenticationEnabled"` IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
IsMagicLoginEnabled bool `json:"isMagicLoginEnabled"`
} }
type ResendVerifyEmailInput struct { type ResendVerifyEmailInput struct {
Email string `json:"email"` Email string `json:"email"`
Identifier string `json:"identifier"`
} }
type ResetPasswordInput struct { type ResetPasswordInput struct {
Token string `json:"token"` Token string `json:"token"`
Password string `json:"password"` Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"` ConfirmPassword string `json:"confirm_password"`
} }
type Response struct { type Response struct {
@ -68,36 +59,67 @@ type Response struct {
} }
type SignUpInput struct { type SignUpInput struct {
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
Email string `json:"email"` Email string `json:"email"`
GivenName *string `json:"given_name"`
FamilyName *string `json:"family_name"`
MiddleName *string `json:"middle_name"`
Nickname *string `json:"nickname"`
Gender *string `json:"gender"`
Birthdate *string `json:"birthdate"`
PhoneNumber *string `json:"phone_number"`
Picture *string `json:"picture"`
Password string `json:"password"` Password string `json:"password"`
ConfirmPassword string `json:"confirmPassword"` ConfirmPassword string `json:"confirm_password"`
Image *string `json:"image"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
} }
type UpdateProfileInput struct { type UpdateProfileInput struct {
OldPassword *string `json:"oldPassword"` OldPassword *string `json:"old_password"`
NewPassword *string `json:"newPassword"` NewPassword *string `json:"new_password"`
ConfirmNewPassword *string `json:"confirmNewPassword"` ConfirmNewPassword *string `json:"confirm_new_password"`
FirstName *string `json:"firstName"`
LastName *string `json:"lastName"`
Image *string `json:"image"`
Email *string `json:"email"` Email *string `json:"email"`
GivenName *string `json:"given_name"`
FamilyName *string `json:"family_name"`
MiddleName *string `json:"middle_name"`
Nickname *string `json:"nickname"`
Gender *string `json:"gender"`
Birthdate *string `json:"birthdate"`
PhoneNumber *string `json:"phone_number"`
Picture *string `json:"picture"`
}
type UpdateUserInput struct {
ID string `json:"id"`
Email *string `json:"email"`
GivenName *string `json:"given_name"`
FamilyName *string `json:"family_name"`
MiddleName *string `json:"middle_name"`
Nickname *string `json:"nickname"`
Gender *string `json:"gender"`
Birthdate *string `json:"birthdate"`
PhoneNumber *string `json:"phone_number"`
Picture *string `json:"picture"`
Roles []*string `json:"roles"`
} }
type User struct { type User struct {
ID string `json:"id"` ID string `json:"id"`
Email string `json:"email"` Email string `json:"email"`
SignupMethod string `json:"signupMethod"` EmailVerified bool `json:"email_verified"`
FirstName *string `json:"firstName"` SignupMethods string `json:"signup_methods"`
LastName *string `json:"lastName"` GivenName *string `json:"given_name"`
EmailVerifiedAt *int64 `json:"emailVerifiedAt"` FamilyName *string `json:"family_name"`
Image *string `json:"image"` MiddleName *string `json:"middle_name"`
CreatedAt *int64 `json:"createdAt"` Nickname *string `json:"nickname"`
UpdatedAt *int64 `json:"updatedAt"` PreferredUsername *string `json:"preferred_username"`
Gender *string `json:"gender"`
Birthdate *string `json:"birthdate"`
PhoneNumber *string `json:"phone_number"`
PhoneNumberVerified *bool `json:"phone_number_verified"`
Picture *string `json:"picture"`
Roles []string `json:"roles"` Roles []string `json:"roles"`
CreatedAt *int64 `json:"created_at"`
UpdatedAt *int64 `json:"updated_at"`
} }
type VerificationRequest struct { type VerificationRequest struct {
@ -106,8 +128,8 @@ type VerificationRequest struct {
Token *string `json:"token"` Token *string `json:"token"`
Email *string `json:"email"` Email *string `json:"email"`
Expires *int64 `json:"expires"` Expires *int64 `json:"expires"`
CreatedAt *int64 `json:"createdAt"` CreatedAt *int64 `json:"created_at"`
UpdatedAt *int64 `json:"updatedAt"` UpdatedAt *int64 `json:"updated_at"`
} }
type VerifyEmailInput struct { type VerifyEmailInput struct {

View File

@ -7,26 +7,33 @@ scalar Any
type Meta { type Meta {
version: String! version: String!
isGoogleLoginEnabled: Boolean! is_google_login_enabled: Boolean!
isFacebookLoginEnabled: Boolean! is_facebook_login_enabled: Boolean!
isTwitterLoginEnabled: Boolean! is_github_login_enabled: Boolean!
isGithubLoginEnabled: Boolean! is_email_verification_enabled: Boolean!
isEmailVerificationEnabled: Boolean! is_basic_authentication_enabled: Boolean!
isBasicAuthenticationEnabled: Boolean! is_magic_link_login_enabled: Boolean!
isMagicLoginEnabled: Boolean!
} }
type User { type User {
id: ID! id: ID!
email: String! email: String!
signupMethod: String! email_verified: Boolean!
firstName: String signup_methods: String!
lastName: String given_name: String
emailVerifiedAt: Int64 family_name: String
image: String middle_name: String
createdAt: Int64 nickname: String
updatedAt: Int64 # defaults to email
preferred_username: String
gender: String
birthdate: String
phone_number: String
phone_number_verified: Boolean
picture: String
roles: [String!]! roles: [String!]!
created_at: Int64
updated_at: Int64
} }
type VerificationRequest { type VerificationRequest {
@ -35,8 +42,8 @@ type VerificationRequest {
token: String token: String
email: String email: String
expires: Int64 expires: Int64
createdAt: Int64 created_at: Int64
updatedAt: Int64 updated_at: Int64
} }
type Error { type Error {
@ -46,8 +53,8 @@ type Error {
type AuthResponse { type AuthResponse {
message: String! message: String!
accessToken: String access_token: String
accessTokenExpiresAt: Int64 expires_at: Int64
user: User user: User
} }
@ -56,12 +63,17 @@ type Response {
} }
input SignUpInput { input SignUpInput {
firstName: String
lastName: String
email: String! email: String!
given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
password: String! password: String!
confirmPassword: String! confirm_password: String!
image: String
roles: [String!] roles: [String!]
} }
@ -77,25 +89,35 @@ input VerifyEmailInput {
input ResendVerifyEmailInput { input ResendVerifyEmailInput {
email: String! email: String!
identifier: String!
} }
input UpdateProfileInput { input UpdateProfileInput {
oldPassword: String old_password: String
newPassword: String new_password: String
confirmNewPassword: String confirm_new_password: String
firstName: String
lastName: String
image: String
email: String email: String
# roles: [String] given_name: String
family_name: String
middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
} }
input AdminUpdateUserInput { input UpdateUserInput {
id: ID! id: ID!
email: String email: String
firstName: String given_name: String
lastName: String family_name: String
image: String middle_name: String
nickname: String
gender: String
birthdate: String
phone_number: String
picture: String
roles: [String] roles: [String]
} }
@ -106,14 +128,14 @@ input ForgotPasswordInput {
input ResetPasswordInput { input ResetPasswordInput {
token: String! token: String!
password: String! password: String!
confirmPassword: String! confirm_password: String!
} }
input DeleteUserInput { input DeleteUserInput {
email: String! email: String!
} }
input MagicLoginInput { input MagicLinkLoginInput {
email: String! email: String!
roles: [String!] roles: [String!]
} }
@ -121,21 +143,23 @@ input MagicLoginInput {
type Mutation { type Mutation {
signup(params: SignUpInput!): AuthResponse! signup(params: SignUpInput!): AuthResponse!
login(params: LoginInput!): AuthResponse! login(params: LoginInput!): AuthResponse!
magicLogin(params: MagicLoginInput!): Response! magic_link_login(params: MagicLinkLoginInput!): Response!
logout: Response! logout: Response!
updateProfile(params: UpdateProfileInput!): Response! update_profile(params: UpdateProfileInput!): Response!
adminUpdateUser(params: AdminUpdateUserInput!): User! verify_email(params: VerifyEmailInput!): AuthResponse!
verifyEmail(params: VerifyEmailInput!): AuthResponse! resend_verify_email(params: ResendVerifyEmailInput!): Response!
resendVerifyEmail(params: ResendVerifyEmailInput!): Response! forgot_password(params: ForgotPasswordInput!): Response!
forgotPassword(params: ForgotPasswordInput!): Response! reset_password(params: ResetPasswordInput!): Response!
resetPassword(params: ResetPasswordInput!): Response! # admin only apis
deleteUser(params: DeleteUserInput!): Response! _delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User!
} }
type Query { type Query {
meta: Meta! meta: Meta!
users: [User!]! session(roles: [String!]): AuthResponse
token(roles: [String!]): AuthResponse
profile: User! profile: User!
verificationRequests: [VerificationRequest!]! # admin only apis
_users: [User!]!
_verification_requests: [VerificationRequest!]!
} }

View File

@ -19,8 +19,8 @@ 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) { func (r *mutationResolver) MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
return resolvers.MagicLogin(ctx, params) return resolvers.MagicLinkLogin(ctx, params)
} }
func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) { func (r *mutationResolver) Logout(ctx context.Context) (*model.Response, error) {
@ -31,10 +31,6 @@ func (r *mutationResolver) UpdateProfile(ctx context.Context, params model.Updat
return resolvers.UpdateProfile(ctx, params) return resolvers.UpdateProfile(ctx, params)
} }
func (r *mutationResolver) AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error) {
return resolvers.AdminUpdateUser(ctx, params)
}
func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) { func (r *mutationResolver) VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.AuthResponse, error) {
return resolvers.VerifyEmail(ctx, params) return resolvers.VerifyEmail(ctx, params)
} }
@ -55,22 +51,26 @@ func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUs
return resolvers.DeleteUser(ctx, params) return resolvers.DeleteUser(ctx, params)
} }
func (r *mutationResolver) UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) {
return resolvers.UpdateUser(ctx, params)
}
func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) { func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) {
return resolvers.Meta(ctx) return resolvers.Meta(ctx)
} }
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) { func (r *queryResolver) Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
return resolvers.Users(ctx) return resolvers.Session(ctx, roles)
}
func (r *queryResolver) Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
return resolvers.Token(ctx, roles)
} }
func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) { func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) {
return resolvers.Profile(ctx) return resolvers.Profile(ctx)
} }
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
return resolvers.Users(ctx)
}
func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) { func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) {
return resolvers.VerificationRequests(ctx) return resolvers.VerificationRequests(ctx)
} }
@ -81,5 +81,7 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol
// Query returns generated.QueryResolver implementation. // Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} } func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type mutationResolver struct{ *Resolver } type (
type queryResolver struct{ *Resolver } mutationResolver struct{ *Resolver }
queryResolver struct{ *Resolver }
)

View File

@ -43,26 +43,10 @@ func processGoogleUserInfo(code string) (db.User, error) {
return user, fmt.Errorf("unable to verify id_token: %s", err.Error()) return user, fmt.Errorf("unable to verify id_token: %s", err.Error())
} }
// Extract custom claims if err := idToken.Claims(&user); err != nil {
var claims struct {
Email string `json:"email"`
Picture string `json:"picture"`
GivenName string `json:"given_name"`
FamilyName string `json:"family_name"`
Verified bool `json:"email_verified"`
}
if err := idToken.Claims(&claims); err != nil {
return user, fmt.Errorf("unable to extract claims") return user, fmt.Errorf("unable to extract claims")
} }
user = db.User{
FirstName: claims.GivenName,
LastName: claims.FamilyName,
Image: claims.Picture,
Email: claims.Email,
EmailVerifiedAt: time.Now().Unix(),
}
return user, nil return user, nil
} }
@ -104,12 +88,14 @@ func processGithubUserInfo(code string) (db.User, error) {
if len(name) > 1 && strings.TrimSpace(name[1]) != "" { if len(name) > 1 && strings.TrimSpace(name[1]) != "" {
lastName = name[0] lastName = name[0]
} }
picture := userRawData["avatar_url"]
user = db.User{ user = db.User{
FirstName: firstName, GivenName: &firstName,
LastName: lastName, FamilyName: &lastName,
Image: userRawData["avatar_url"], Picture: &picture,
Email: userRawData["email"], Email: userRawData["email"],
EmailVerifiedAt: time.Now().Unix(),
} }
return user, nil return user, nil
@ -146,12 +132,15 @@ func processFacebookUserInfo(code string) (db.User, error) {
picObject := userRawData["picture"].(map[string]interface{})["data"] picObject := userRawData["picture"].(map[string]interface{})["data"]
picDataObject := picObject.(map[string]interface{}) picDataObject := picObject.(map[string]interface{})
firstName := fmt.Sprintf("%v", userRawData["first_name"])
lastName := fmt.Sprintf("%v", userRawData["last_name"])
picture := fmt.Sprintf("%v", picDataObject["url"])
user = db.User{ user = db.User{
FirstName: fmt.Sprintf("%v", userRawData["first_name"]), GivenName: &firstName,
LastName: fmt.Sprintf("%v", userRawData["last_name"]), FamilyName: &lastName,
Image: fmt.Sprintf("%v", picDataObject["url"]), Picture: &picture,
Email: email, Email: email,
EmailVerifiedAt: time.Now().Unix(),
} }
return user, nil return user, nil
@ -202,7 +191,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
if err != nil { if err != nil {
// user not registered, register user and generate session token // user not registered, register user and generate session token
user.SignupMethod = provider user.SignupMethods = provider
// make sure inputRoles don't include protected roles // make sure inputRoles don't include protected roles
hasProtectedRole := false hasProtectedRole := false
for _, ir := range inputRoles { for _, ir := range inputRoles {
@ -217,16 +206,18 @@ func OAuthCallbackHandler() gin.HandlerFunc {
} }
user.Roles = strings.Join(inputRoles, ",") user.Roles = strings.Join(inputRoles, ",")
now := time.Now().Unix()
user.EmailVerifiedAt = &now
user, _ = db.Mgr.AddUser(user) user, _ = db.Mgr.AddUser(user)
} else { } else {
// user exists in db, check if method was google // user exists in db, check if method was google
// if not append google to existing signup method and save it // if not append google to existing signup method and save it
signupMethod := existingUser.SignupMethod signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, provider) { if !strings.Contains(signupMethod, provider) {
signupMethod = signupMethod + "," + provider signupMethod = signupMethod + "," + provider
} }
user.SignupMethod = signupMethod user.SignupMethods = signupMethod
user.Password = existingUser.Password user.Password = existingUser.Password
// There multiple scenarios with roles here in social login // There multiple scenarios with roles here in social login
@ -262,7 +253,6 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.Roles = existingUser.Roles user.Roles = existingUser.Roles
} }
user.Key = existingUser.Key user.Key = existingUser.Key
user.ObjectID = existingUser.ObjectID
user.ID = existingUser.ID user.ID = existingUser.ID
user, err = db.Mgr.UpdateUser(user) user, err = db.Mgr.UpdateUser(user)
} }
@ -274,15 +264,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, inputRoles) accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, inputRoles)
utils.SetCookie(c, accessToken) utils.SetCookie(c, accessToken)
session.SetToken(userIdStr, accessToken, refreshToken) session.SetToken(userIdStr, accessToken, refreshToken)
go func() { utils.CreateSession(user.ID, c)
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(c.Request),
IP: utils.GetIP(c.Request),
}
db.Mgr.AddSession(sessionData)
}()
c.Redirect(http.StatusTemporaryRedirect, redirectURL) c.Redirect(http.StatusTemporaryRedirect, redirectURL)
} }

View File

@ -1,7 +1,6 @@
package handlers package handlers
import ( import (
"fmt"
"net/http" "net/http"
"strings" "strings"
"time" "time"
@ -46,29 +45,21 @@ func VerifyEmailHandler() gin.HandlerFunc {
} }
// update email_verified_at in users table // update email_verified_at in users table
if user.EmailVerifiedAt <= 0 { if user.EmailVerifiedAt == nil {
user.EmailVerifiedAt = time.Now().Unix() now := time.Now().Unix()
user.EmailVerifiedAt = &now
db.Mgr.UpdateUser(user) db.Mgr.UpdateUser(user)
} }
// delete from verification table // delete from verification table
db.Mgr.DeleteVerificationRequest(verificationRequest) db.Mgr.DeleteVerificationRequest(verificationRequest)
userIdStr := fmt.Sprintf("%v", user.ID)
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles) refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, roles) accessToken, _, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, accessToken, refreshToken) session.SetToken(user.ID, accessToken, refreshToken)
go func() { utils.CreateSession(user.ID, c)
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(c.Request),
IP: utils.GetIP(c.Request),
}
db.Mgr.AddSession(sessionData)
}()
utils.SetCookie(c, accessToken) utils.SetCookie(c, accessToken)
c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL) c.Redirect(http.StatusTemporaryRedirect, claim.RedirectURL)
} }

View File

@ -1,44 +0,0 @@
package integration_test
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/env"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
)
func TestCors(t *testing.T) {
constants.ENV_PATH = "../../.env.local"
env.InitEnv()
r := gin.Default()
r.Use(location.Default())
r.Use(middlewares.GinContextToContextMiddleware())
r.Use(middlewares.CORSMiddleware())
allowedOrigin := "http://localhost:8080" // The allowed origin that you want to check
notAllowedOrigin := "http://myapp.com"
server := httptest.NewServer(r)
defer server.Close()
client := &http.Client{}
req, _ := http.NewRequest(
"GET",
"http://"+server.Listener.Addr().String()+"/api",
nil,
)
req.Header.Add("Origin", allowedOrigin)
get, _ := client.Do(req)
// You should get your origin (or a * depending on your config) if the
// passed origin is allowed.
o := get.Header.Get("Access-Control-Allow-Origin")
assert.NotEqual(t, o, notAllowedOrigin, "Origins should not match")
assert.Equal(t, o, allowedOrigin, "Origins don't match")
}

View File

@ -1,44 +1,41 @@
package main package main
import ( import (
"flag"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/env" "github.com/authorizerdev/authorizer/server/env"
"github.com/authorizerdev/authorizer/server/handlers" "github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/authorizerdev/authorizer/server/oauth" "github.com/authorizerdev/authorizer/server/oauth"
"github.com/authorizerdev/authorizer/server/router"
"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/gin-contrib/location"
"github.com/gin-gonic/gin"
) )
func main() { func main() {
env.ARG_DB_URL = flag.String("database_url", "", "Database connection string")
env.ARG_DB_TYPE = flag.String("database_type", "", "Database type, possible values are postgres,mysql,sqlite")
env.ARG_AUTHORIZER_URL = flag.String("authorizer_url", "", "URL for authorizer instance, eg: https://xyz.herokuapp.com")
env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
flag.Parse()
env.InitEnv() env.InitEnv()
db.InitDB() db.InitDB()
session.InitSession() session.InitSession()
oauth.InitOAuth() oauth.InitOAuth()
utils.InitServer() utils.InitServer()
r := gin.Default() router := router.InitRouter()
r.Use(location.Default())
r.Use(middlewares.GinContextToContextMiddleware())
r.Use(middlewares.CORSMiddleware())
r.GET("/", handlers.PlaygroundHandler()) // login wall app related routes.
r.POST("/graphql", handlers.GraphqlHandler()) // if we put them in router file then tests would fail as templates or build path will be different
r.GET("/verify_email", handlers.VerifyEmailHandler()) router.LoadHTMLGlob("templates/*")
r.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler()) app := router.Group("/app")
r.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler())
// login wall app related routes
r.LoadHTMLGlob("templates/*")
app := r.Group("/app")
{ {
app.Static("/build", "app/build") app.Static("/build", "app/build")
app.GET("/", handlers.AppHandler()) app.GET("/", handlers.AppHandler())
app.GET("/reset-password", handlers.AppHandler()) app.GET("/reset-password", handlers.AppHandler())
} }
router.Run(":" + constants.PORT)
r.Run()
} }

View File

@ -14,7 +14,7 @@ func GinContextToContextMiddleware() gin.HandlerFunc {
if constants.AUTHORIZER_URL == "" { if constants.AUTHORIZER_URL == "" {
url := location.Get(c) url := location.Get(c)
constants.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host constants.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host
log.Println("=> authorizer url:", constants.AUTHORIZER_URL) log.Println("authorizer url:", constants.AUTHORIZER_URL)
} }
ctx := context.WithValue(c.Request.Context(), "GinContextKey", c) ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
c.Request = c.Request.WithContext(ctx) c.Request = c.Request.WithContext(ctx)

View File

@ -32,15 +32,15 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
return res, fmt.Errorf(`user with this email not found`) return res, fmt.Errorf(`user with this email not found`)
} }
if !strings.Contains(user.SignupMethod, enum.BasicAuth.String()) { if !strings.Contains(user.SignupMethods, enum.BasicAuth.String()) {
return res, fmt.Errorf(`user has not signed up email & password`) return res, fmt.Errorf(`user has not signed up email & password`)
} }
if user.EmailVerifiedAt <= 0 { if user.EmailVerifiedAt == nil {
return res, fmt.Errorf(`email not verified`) return res, fmt.Errorf(`email not verified`)
} }
err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(params.Password)) err = bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(params.Password))
if err != nil { if err != nil {
log.Println("compare password error:", err) log.Println("compare password error:", err)
@ -55,38 +55,18 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
roles = params.Roles roles = params.Roles
} }
userIdStr := fmt.Sprintf("%v", user.ID)
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles) refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles) accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, accessToken, refreshToken) session.SetToken(user.ID, accessToken, refreshToken)
go func() { utils.CreateSession(user.ID, gc)
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.AddSession(sessionData)
}()
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Logged in successfully`, Message: `Logged in successfully`,
AccessToken: &accessToken, AccessToken: &accessToken,
AccessTokenExpiresAt: &expiresAt, ExpiresAt: &expiresAt,
User: &model.User{ User: utils.GetResponseUserData(user),
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
},
} }
utils.SetCookie(gc, accessToken) utils.SetCookie(gc, accessToken)

View File

@ -14,10 +14,10 @@ import (
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Response, error) { func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
var res *model.Response var res *model.Response
if constants.DISABLE_MAGIC_LOGIN { if constants.DISABLE_MAGIC_LINK_LOGIN {
return res, fmt.Errorf(`magic link login is disabled for this instance`) return res, fmt.Errorf(`magic link login is disabled for this instance`)
} }
@ -37,7 +37,7 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo
existingUser, err := db.Mgr.GetUserByEmail(params.Email) existingUser, err := db.Mgr.GetUserByEmail(params.Email)
if err != nil { if err != nil {
user.SignupMethod = enum.MagicLink.String() user.SignupMethods = enum.MagicLinkLogin.String()
// define roles for new user // define roles for new user
if len(params.Roles) > 0 { if len(params.Roles) > 0 {
// check if roles exists // check if roles exists
@ -86,12 +86,12 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo
user.Roles = existingUser.Roles user.Roles = existingUser.Roles
} }
signupMethod := existingUser.SignupMethod signupMethod := existingUser.SignupMethods
if !strings.Contains(signupMethod, enum.MagicLink.String()) { if !strings.Contains(signupMethod, enum.MagicLinkLogin.String()) {
signupMethod = signupMethod + "," + enum.MagicLink.String() signupMethod = signupMethod + "," + enum.MagicLinkLogin.String()
} }
user.SignupMethod = signupMethod user.SignupMethods = signupMethod
user, _ = db.Mgr.UpdateUser(user) user, _ = db.Mgr.UpdateUser(user)
if err != nil { if err != nil {
log.Println("error updating user:", err) log.Println("error updating user:", err)
@ -100,7 +100,7 @@ func MagicLogin(ctx context.Context, params model.MagicLoginInput) (*model.Respo
if !constants.DISABLE_EMAIL_VERIFICATION { if !constants.DISABLE_EMAIL_VERIFICATION {
// insert verification request // insert verification request
verificationType := enum.MagicLink.String() verificationType := enum.MagicLinkLogin.String()
token, err := utils.CreateVerificationToken(params.Email, verificationType) token, err := utils.CreateVerificationToken(params.Email, verificationType)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)

View File

@ -3,7 +3,6 @@ package resolvers
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -41,20 +40,7 @@ func Profile(ctx context.Context) (*model.User, error) {
return res, err return res, err
} }
userIdStr := fmt.Sprintf("%v", user.ID) res = utils.GetResponseUserData(user)
res = &model.User{
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
return res, nil return res, nil
} }

View File

@ -20,18 +20,28 @@ func ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput)
return res, fmt.Errorf("invalid email") return res, fmt.Errorf("invalid email")
} }
verificationRequest, err := db.Mgr.GetVerificationByEmail(params.Email) if !utils.IsValidVerificationIdentifier(params.Identifier) {
return res, fmt.Errorf("invalid identifier")
}
verificationRequest, err := db.Mgr.GetVerificationByEmail(params.Email, params.Identifier)
if err != nil { if err != nil {
return res, fmt.Errorf(`verification request not found`) return res, fmt.Errorf(`verification request not found`)
} }
token, err := utils.CreateVerificationToken(params.Email, verificationRequest.Identifier) // delete current verification and create new one
err = db.Mgr.DeleteVerificationRequest(verificationRequest)
if err != nil {
log.Println("error deleting verification request:", err)
}
token, err := utils.CreateVerificationToken(params.Email, params.Identifier)
if err != nil { if err != nil {
log.Println(`error generating token`, err) log.Println(`error generating token`, err)
} }
db.Mgr.AddVerification(db.VerificationRequest{ db.Mgr.AddVerification(db.VerificationRequest{
Token: token, Token: token,
Identifier: verificationRequest.Identifier, Identifier: params.Identifier,
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
Email: params.Email, Email: params.Email,
}) })

View File

@ -39,13 +39,13 @@ func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model
} }
password, _ := utils.HashPassword(params.Password) password, _ := utils.HashPassword(params.Password)
user.Password = password user.Password = &password
signupMethod := user.SignupMethod signupMethod := user.SignupMethods
if !strings.Contains(signupMethod, enum.BasicAuth.String()) { if !strings.Contains(signupMethod, enum.BasicAuth.String()) {
signupMethod = signupMethod + "," + enum.BasicAuth.String() signupMethod = signupMethod + "," + enum.BasicAuth.String()
} }
user.SignupMethod = signupMethod user.SignupMethods = signupMethod
// delete from verification table // delete from verification table
db.Mgr.DeleteVerificationRequest(verificationRequest) db.Mgr.DeleteVerificationRequest(verificationRequest)

View File

@ -3,7 +3,6 @@ package resolvers
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"time" "time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
@ -14,7 +13,7 @@ import (
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) { func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
var res *model.AuthResponse var res *model.AuthResponse
gc, err := utils.GinContextFromContext(ctx) gc, err := utils.GinContextFromContext(ctx)
@ -67,34 +66,15 @@ func Token(ctx context.Context, roles []string) (*model.AuthResponse, error) {
session.DeleteVerificationRequest(userIdStr, token) session.DeleteVerificationRequest(userIdStr, token)
token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRoles) token, expiresAt, _ = utils.CreateAuthToken(user, enum.AccessToken, claimRoles)
session.SetToken(userIdStr, token, currentRefreshToken) session.SetToken(userIdStr, token, currentRefreshToken)
go func() { utils.CreateSession(user.ID, gc)
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.AddSession(sessionData)
}()
} }
utils.SetCookie(gc, token) utils.SetCookie(gc, token)
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Token verified`, Message: `Token verified`,
AccessToken: &token, AccessToken: &token,
AccessTokenExpiresAt: &expiresAt, ExpiresAt: &expiresAt,
User: &model.User{ User: utils.GetResponseUserData(user),
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
},
} }
return res, nil return res, nil
} }

View File

@ -54,10 +54,10 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
log.Println("user with email " + params.Email + " not found") log.Println("user with email " + params.Email + " not found")
} }
if existingUser.EmailVerifiedAt > 0 { if existingUser.EmailVerifiedAt != nil {
// email is verified // email is verified
return res, fmt.Errorf(`%s has already signed up`, params.Email) return res, fmt.Errorf(`%s has already signed up`, params.Email)
} else if existingUser.ID != "" && existingUser.EmailVerifiedAt <= 0 { } else if existingUser.ID != "" && existingUser.EmailVerifiedAt == nil {
return res, fmt.Errorf("%s has already signed up. please complete the email verification process or reset the password", params.Email) return res, fmt.Errorf("%s has already signed up. please complete the email verification process or reset the password", params.Email)
} }
@ -68,19 +68,44 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
user.Roles = strings.Join(inputRoles, ",") user.Roles = strings.Join(inputRoles, ",")
password, _ := utils.HashPassword(params.Password) password, _ := utils.HashPassword(params.Password)
user.Password = password user.Password = &password
if params.FirstName != nil { if params.GivenName != nil {
user.FirstName = *params.FirstName user.GivenName = params.GivenName
} }
if params.LastName != nil { if params.FamilyName != nil {
user.LastName = *params.LastName user.FamilyName = params.FamilyName
} }
user.SignupMethod = enum.BasicAuth.String() if params.MiddleName != nil {
user.MiddleName = params.MiddleName
}
if params.Nickname != nil {
user.Nickname = params.Nickname
}
if params.Gender != nil {
user.Gender = params.Gender
}
if params.Birthdate != nil {
user.Birthdate = params.Birthdate
}
if params.PhoneNumber != nil {
user.PhoneNumber = params.PhoneNumber
}
if params.Picture != nil {
user.Picture = params.Picture
}
user.SignupMethods = enum.BasicAuth.String()
if constants.DISABLE_EMAIL_VERIFICATION { if constants.DISABLE_EMAIL_VERIFICATION {
user.EmailVerifiedAt = time.Now().Unix() now := time.Now().Unix()
user.EmailVerifiedAt = &now
} }
user, err = db.Mgr.AddUser(user) user, err = db.Mgr.AddUser(user)
if err != nil { if err != nil {
@ -88,18 +113,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
} }
userIdStr := fmt.Sprintf("%v", user.ID) userIdStr := fmt.Sprintf("%v", user.ID)
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
userToReturn := &model.User{ userToReturn := utils.GetResponseUserData(user)
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
if !constants.DISABLE_EMAIL_VERIFICATION { if !constants.DISABLE_EMAIL_VERIFICATION {
// insert verification request // insert verification request
@ -131,19 +145,11 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles) accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, accessToken, refreshToken) session.SetToken(userIdStr, accessToken, refreshToken)
go func() { utils.CreateSession(user.ID, gc)
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.AddSession(sessionData)
}()
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Signed up successfully.`, Message: `Signed up successfully.`,
AccessToken: &accessToken, AccessToken: &accessToken,
AccessTokenExpiresAt: &expiresAt, ExpiresAt: &expiresAt,
User: userToReturn, User: userToReturn,
} }

View File

@ -40,7 +40,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
} }
// validate if all params are not empty // validate if all params are not empty
if params.FirstName == nil && params.LastName == nil && params.Image == nil && params.OldPassword == nil && params.Email == nil { if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.OldPassword == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil {
return res, fmt.Errorf("please enter atleast one param to update") return res, fmt.Errorf("please enter atleast one param to update")
} }
@ -50,20 +50,40 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
return res, err return res, err
} }
if params.FirstName != nil && user.FirstName != *params.FirstName { if params.GivenName != nil && user.GivenName != params.GivenName {
user.FirstName = *params.FirstName user.GivenName = params.GivenName
} }
if params.LastName != nil && user.LastName != *params.LastName { if params.FamilyName != nil && user.FamilyName != params.FamilyName {
user.LastName = *params.LastName user.FamilyName = params.FamilyName
} }
if params.Image != nil && user.Image != *params.Image { if params.MiddleName != nil && user.MiddleName != params.MiddleName {
user.Image = *params.Image user.MiddleName = params.MiddleName
}
if params.Nickname != nil && user.Nickname != params.Nickname {
user.Nickname = params.Nickname
}
if params.Birthdate != nil && user.Birthdate != params.Birthdate {
user.Birthdate = params.Birthdate
}
if params.Gender != nil && user.Gender != params.Gender {
user.Gender = params.Gender
}
if params.PhoneNumber != nil && user.PhoneNumber != params.PhoneNumber {
user.PhoneNumber = params.PhoneNumber
}
if params.Picture != nil && user.Picture != params.Picture {
user.Picture = params.Picture
} }
if params.OldPassword != nil { if params.OldPassword != nil {
if err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(*params.OldPassword)); err != nil { if err = bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(*params.OldPassword)); err != nil {
return res, fmt.Errorf("incorrect old password") return res, fmt.Errorf("incorrect old password")
} }
@ -81,7 +101,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
password, _ := utils.HashPassword(*params.NewPassword) password, _ := utils.HashPassword(*params.NewPassword)
user.Password = password user.Password = &password
} }
hasEmailChanged := false hasEmailChanged := false
@ -93,7 +113,8 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
} }
newEmail := strings.ToLower(*params.Email) newEmail := strings.ToLower(*params.Email)
// check if user with new email exists // check if user with new email exists
_, err = db.Mgr.GetUserByEmail(newEmail) _, err := db.Mgr.GetUserByEmail(newEmail)
// err = nil means user exists // err = nil means user exists
if err == nil { if err == nil {
return res, fmt.Errorf("user with this email address already exists") return res, fmt.Errorf("user with this email address already exists")
@ -103,7 +124,7 @@ func UpdateProfile(ctx context.Context, params model.UpdateProfileInput) (*model
utils.DeleteCookie(gc) utils.DeleteCookie(gc)
user.Email = newEmail user.Email = newEmail
user.EmailVerifiedAt = 0 user.EmailVerifiedAt = nil
hasEmailChanged = true hasEmailChanged = true
// insert verification request // insert verification request
verificationType := enum.UpdateEmail.String() verificationType := enum.UpdateEmail.String()

View File

@ -15,7 +15,7 @@ import (
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*model.User, error) { func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error) {
gc, err := utils.GinContextFromContext(ctx) gc, err := utils.GinContextFromContext(ctx)
var res *model.User var res *model.User
if err != nil { if err != nil {
@ -26,7 +26,7 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
return res, fmt.Errorf("unauthorized") return res, fmt.Errorf("unauthorized")
} }
if params.FirstName == nil && params.LastName == nil && params.Image == nil && params.Email == nil && params.Roles == nil { if params.GivenName == nil && params.FamilyName == nil && params.Picture == nil && params.MiddleName == nil && params.Nickname == nil && params.Email == nil && params.Birthdate == nil && params.Gender == nil && params.PhoneNumber == nil && params.Roles == nil {
return res, fmt.Errorf("please enter atleast one param to update") return res, fmt.Errorf("please enter atleast one param to update")
} }
@ -35,16 +35,36 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
return res, fmt.Errorf(`User not found`) return res, fmt.Errorf(`User not found`)
} }
if params.FirstName != nil && user.FirstName != *params.FirstName { if params.GivenName != nil && user.GivenName != params.GivenName {
user.FirstName = *params.FirstName user.GivenName = params.GivenName
} }
if params.LastName != nil && user.LastName != *params.LastName { if params.FamilyName != nil && user.FamilyName != params.FamilyName {
user.LastName = *params.LastName user.FamilyName = params.FamilyName
} }
if params.Image != nil && user.Image != *params.Image { if params.MiddleName != nil && user.MiddleName != params.MiddleName {
user.Image = *params.Image user.MiddleName = params.MiddleName
}
if params.Nickname != nil && user.Nickname != params.Nickname {
user.Nickname = params.Nickname
}
if params.Birthdate != nil && user.Birthdate != params.Birthdate {
user.Birthdate = params.Birthdate
}
if params.Gender != nil && user.Gender != params.Gender {
user.Gender = params.Gender
}
if params.PhoneNumber != nil && user.PhoneNumber != params.PhoneNumber {
user.PhoneNumber = params.PhoneNumber
}
if params.Picture != nil && user.Picture != params.Picture {
user.Picture = params.Picture
} }
if params.Email != nil && user.Email != *params.Email { if params.Email != nil && user.Email != *params.Email {
@ -64,7 +84,7 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
utils.DeleteCookie(gc) utils.DeleteCookie(gc)
user.Email = newEmail user.Email = newEmail
user.EmailVerifiedAt = 0 user.EmailVerifiedAt = nil
// insert verification request // insert verification request
verificationType := enum.UpdateEmail.String() verificationType := enum.UpdateEmail.String()
token, err := utils.CreateVerificationToken(newEmail, verificationType) token, err := utils.CreateVerificationToken(newEmail, verificationType)
@ -117,9 +137,9 @@ func AdminUpdateUser(ctx context.Context, params model.AdminUpdateUserInput) (*m
res = &model.User{ res = &model.User{
ID: params.ID, ID: params.ID,
Email: user.Email, Email: user.Email,
Image: &user.Image, Picture: user.Picture,
FirstName: &user.FirstName, GivenName: user.GivenName,
LastName: &user.LastName, FamilyName: user.FamilyName,
Roles: strings.Split(user.Roles, ","), Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt, CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt, UpdatedAt: &user.UpdatedAt,

View File

@ -3,7 +3,6 @@ package resolvers
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -27,17 +26,7 @@ func Users(ctx context.Context) ([]*model.User, error) {
} }
for i := 0; i < len(users); i++ { for i := 0; i < len(users); i++ {
res = append(res, &model.User{ res = append(res, utils.GetResponseUserData(users[i]))
ID: fmt.Sprintf("%v", users[i].ID),
Email: users[i].Email,
SignupMethod: users[i].SignupMethod,
FirstName: &users[i].FirstName,
LastName: &users[i].LastName,
EmailVerifiedAt: &users[i].EmailVerifiedAt,
Roles: strings.Split(users[i].Roles, ","),
CreatedAt: &users[i].CreatedAt,
UpdatedAt: &users[i].UpdatedAt,
})
} }
return res, nil return res, nil

View File

@ -37,44 +37,25 @@ func VerifyEmail(ctx context.Context, params model.VerifyEmailInput) (*model.Aut
} }
// update email_verified_at in users table // update email_verified_at in users table
user.EmailVerifiedAt = time.Now().Unix() now := time.Now().Unix()
user.EmailVerifiedAt = &now
db.Mgr.UpdateUser(user) db.Mgr.UpdateUser(user)
// delete from verification table // delete from verification table
db.Mgr.DeleteVerificationRequest(verificationRequest) db.Mgr.DeleteVerificationRequest(verificationRequest)
userIdStr := fmt.Sprintf("%v", user.ID)
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles) refreshToken, _, _ := utils.CreateAuthToken(user, enum.RefreshToken, roles)
accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles) accessToken, expiresAt, _ := utils.CreateAuthToken(user, enum.AccessToken, roles)
session.SetToken(userIdStr, accessToken, refreshToken) session.SetToken(user.ID, accessToken, refreshToken)
go func() { utils.CreateSession(user.ID, gc)
sessionData := db.Session{
UserID: user.ID,
UserAgent: utils.GetUserAgent(gc.Request),
IP: utils.GetIP(gc.Request),
}
db.Mgr.AddSession(sessionData)
}()
res = &model.AuthResponse{ res = &model.AuthResponse{
Message: `Email verified successfully.`, Message: `Email verified successfully.`,
AccessToken: &accessToken, AccessToken: &accessToken,
AccessTokenExpiresAt: &expiresAt, ExpiresAt: &expiresAt,
User: &model.User{ User: utils.GetResponseUserData(user),
ID: userIdStr,
Email: user.Email,
Image: &user.Image,
FirstName: &user.FirstName,
LastName: &user.LastName,
SignupMethod: user.SignupMethod,
EmailVerifiedAt: &user.EmailVerifiedAt,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
},
} }
utils.SetCookie(gc, accessToken) utils.SetCookie(gc, accessToken)

23
server/router/router.go Normal file
View File

@ -0,0 +1,23 @@
package router
import (
"github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
func InitRouter() *gin.Engine {
router := gin.Default()
router.Use(location.Default())
router.Use(middlewares.GinContextToContextMiddleware())
router.Use(middlewares.CORSMiddleware())
router.GET("/", handlers.PlaygroundHandler())
router.POST("/graphql", handlers.GraphqlHandler())
router.GET("/verify_email", handlers.VerifyEmailHandler())
router.GET("/oauth_login/:oauth_provider", handlers.OAuthLoginHandler())
router.GET("/oauth_callback/:oauth_provider", handlers.OAuthCallbackHandler())
return router
}

View File

@ -26,34 +26,31 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
expiresAt := time.Now().Add(expiryBound).Unix() expiresAt := time.Now().Add(expiryBound).Unix()
resUser := GetResponseUserData(user)
userBytes, _ := json.Marshal(&resUser)
var userMap map[string]interface{}
json.Unmarshal(userBytes, &userMap)
customClaims := jwt.MapClaims{ customClaims := jwt.MapClaims{
"exp": expiresAt, "exp": expiresAt,
"iat": time.Now().Unix(), "iat": time.Now().Unix(),
"token_type": tokenType.String(), "token_type": tokenType.String(),
"email": user.Email,
"id": user.ID,
"allowed_roles": strings.Split(user.Roles, ","), "allowed_roles": strings.Split(user.Roles, ","),
constants.JWT_ROLE_CLAIM: roles, constants.JWT_ROLE_CLAIM: roles,
} }
// check for the extra access token script for k, v := range userMap {
if k != "roles" {
accessTokenScript := os.Getenv("CUSTOM_ACCESS_TOKEN_SCRIPT") customClaims[k] = v
if accessTokenScript != "" { }
userInfo := map[string]interface{}{
"id": user.ID,
"email": user.Email,
"firstName": user.FirstName,
"lastName": user.LastName,
"image": user.Image,
"roles": strings.Split(user.Roles, ","),
"signUpMethods": strings.Split(user.SignupMethod, ","),
} }
// check for the extra access token script
accessTokenScript := os.Getenv("CUSTOM_ACCESS_TOKEN_SCRIPT")
if accessTokenScript != "" {
vm := otto.New() vm := otto.New()
userBytes, _ := json.Marshal(userInfo)
claimBytes, _ := json.Marshal(customClaims)
claimBytes, _ := json.Marshal(customClaims)
vm.Run(fmt.Sprintf(` vm.Run(fmt.Sprintf(`
var user = %s; var user = %s;
var tokenPayload = %s; var tokenPayload = %s;

View File

@ -0,0 +1,16 @@
package utils
import (
"github.com/authorizerdev/authorizer/server/db"
"github.com/gin-gonic/gin"
)
func CreateSession(userId string, c *gin.Context) {
sessionData := db.Session{
UserID: userId,
UserAgent: GetUserAgent(c.Request),
IP: GetIP(c.Request),
}
db.Mgr.AddSession(sessionData)
}

View File

@ -0,0 +1,32 @@
package utils
import (
"strings"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
)
func GetResponseUserData(user db.User) *model.User {
isEmailVerified := user.EmailVerifiedAt != nil
isPhoneVerified := user.PhoneNumberVerifiedAt != nil
return &model.User{
ID: user.ID,
Email: user.Email,
EmailVerified: isEmailVerified,
SignupMethods: user.SignupMethods,
GivenName: user.GivenName,
FamilyName: user.FamilyName,
MiddleName: user.MiddleName,
Nickname: user.Nickname,
PreferredUsername: &user.Email,
Gender: user.Gender,
Birthdate: user.Birthdate,
PhoneNumber: user.PhoneNumber,
PhoneNumberVerified: &isPhoneVerified,
Picture: user.Picture,
Roles: strings.Split(user.Roles, ","),
CreatedAt: &user.CreatedAt,
UpdatedAt: &user.UpdatedAt,
}
}

View File

@ -13,9 +13,8 @@ func GetMetaInfo() model.Meta {
IsGoogleLoginEnabled: constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "", IsGoogleLoginEnabled: constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "",
IsGithubLoginEnabled: constants.GITHUB_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "", IsGithubLoginEnabled: constants.GITHUB_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "",
IsFacebookLoginEnabled: constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "", IsFacebookLoginEnabled: constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "",
IsTwitterLoginEnabled: constants.TWITTER_CLIENT_ID != "" && constants.TWITTER_CLIENT_SECRET != "",
IsBasicAuthenticationEnabled: !constants.DISABLE_BASIC_AUTHENTICATION, IsBasicAuthenticationEnabled: !constants.DISABLE_BASIC_AUTHENTICATION,
IsEmailVerificationEnabled: !constants.DISABLE_EMAIL_VERIFICATION, IsEmailVerificationEnabled: !constants.DISABLE_EMAIL_VERIFICATION,
IsMagicLoginEnabled: !constants.DISABLE_MAGIC_LOGIN, IsMagicLinkLoginEnabled: !constants.DISABLE_MAGIC_LINK_LOGIN,
} }
} }

View File

@ -6,6 +6,7 @@ import (
"strings" "strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -69,6 +70,13 @@ func IsValidRoles(userRoles []string, roles []string) bool {
return valid return valid
} }
func IsValidVerificationIdentifier(identifier string) bool {
if identifier != enum.BasicAuthSignup.String() && identifier != enum.ForgotPassword.String() && identifier != enum.UpdateEmail.String() {
return false
}
return true
}
func IsStringArrayEqual(a, b []string) bool { func IsStringArrayEqual(a, b []string) bool {
if len(a) != len(b) { if len(a) != len(b) {
return false return false

View File

@ -1,34 +0,0 @@
package utils
import (
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/stretchr/testify/assert"
)
func TestIsValidEmail(t *testing.T) {
validEmail := "lakhan@gmail.com"
invalidEmail1 := "lakhan"
invalidEmail2 := "lakhan.me"
assert.True(t, IsValidEmail(validEmail), "it should be valid email")
assert.False(t, IsValidEmail(invalidEmail1), "it should be invalid email")
assert.False(t, IsValidEmail(invalidEmail2), "it should be invalid email")
}
func TestIsValidOrigin(t *testing.T) {
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
// as we trim them off while running the main function
constants.ALLOWED_ORIGINS = []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"}
assert.False(t, IsValidOrigin("http://myapp.com"), "it should be invalid origin")
assert.False(t, IsValidOrigin("http://appgoogle.com"), "it should be invalid origin")
assert.True(t, IsValidOrigin("http://app.google.com"), "it should be valid origin")
assert.False(t, IsValidOrigin("http://app.google.ind"), "it should be invalid origin")
assert.True(t, IsValidOrigin("http://app.google.in"), "it should be valid origin")
assert.True(t, IsValidOrigin("http://xyx.abc.com"), "it should be valid origin")
assert.True(t, IsValidOrigin("http://xyx.abc.in"), "it should be valid origin")
assert.True(t, IsValidOrigin("http://xyxabc.in"), "it should be valid origin")
assert.True(t, IsValidOrigin("http://localhost:8080"), "it should be valid origin")
}

View File

@ -24,7 +24,6 @@ func CreateVerificationToken(email string, tokenType string) (string, error) {
t.Claims = &CustomClaim{ t.Claims = &CustomClaim{
&jwt.StandardClaims{ &jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
}, },
tokenType, tokenType,