Merge pull request #92 from authorizerdev/feat/setup-onboard-apis

feat/setup onboard apis
This commit is contained in:
Lakhan Samani 2021-12-31 17:27:22 +05:30 committed by GitHub
commit 192070c18e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
59 changed files with 3539 additions and 375 deletions

View File

@ -1,19 +1,12 @@
import * as React from 'react'; import * as React from 'react';
import { Text, ChakraProvider } from '@chakra-ui/react'; import { Text, ChakraProvider } from '@chakra-ui/react';
import { MdStar } from 'react-icons/md'; import { MdStar } from 'react-icons/md';
import { BrowserRouter } from 'react-router-dom';
export default function Example() { export default function Example() {
return ( return (
<ChakraProvider> <ChakraProvider>
<Text <BrowserRouter></BrowserRouter>
ml={2}
textTransform="uppercase"
fontSize="xl"
fontWeight="bold"
color="pink.800"
>
Authorizer Dashboard
</Text>
</ChakraProvider> </ChakraProvider>
); );
} }

0
dashboard/src/Router.tsx Normal file
View File

View File

@ -0,0 +1,5 @@
import React from 'react';
export default function AuthLayout() {
return <h1>Auth Layout</h1>;
}

View File

@ -0,0 +1,5 @@
import React from 'react';
export default function DefaultLayout() {
return <h1>Default Layout</h1>;
}

View File

View File

@ -0,0 +1,28 @@
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 adminLoginTests(s TestSetup, t *testing.T) {
t.Run(`should complete admin login`, func(t *testing.T) {
_, ctx := createContext(s)
_, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
AdminSecret: "admin_test",
})
assert.NotNil(t, err)
res, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
AdminSecret: constants.EnvData.ADMIN_SECRET,
})
assert.Nil(t, err)
assert.Greater(t, len(res.AccessToken), 0)
})
}

View File

@ -0,0 +1,28 @@
package test
import (
"log"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func adminSessionTests(s TestSetup, t *testing.T) {
t.Run(`should get admin session`, func(t *testing.T) {
req, ctx := createContext(s)
_, err := resolvers.AdminSession(ctx)
log.Println("error:", err)
assert.NotNil(t, err)
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
assert.Nil(t, err)
req.Header.Add("Authorization", "Bearer "+h)
res, err := resolvers.AdminSession(ctx)
assert.Nil(t, err)
assert.Greater(t, len(res.AccessToken), 0)
})
}

View File

@ -0,0 +1,28 @@
package test
import (
"log"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func configTests(s TestSetup, t *testing.T) {
t.Run(`should get config`, func(t *testing.T) {
req, ctx := createContext(s)
_, err := resolvers.ConfigResolver(ctx)
log.Println("error:", err)
assert.NotNil(t, err)
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
assert.Nil(t, err)
req.Header.Add("Authorization", "Bearer "+h)
res, err := resolvers.ConfigResolver(ctx)
assert.Nil(t, err)
assert.Equal(t, *res.AdminSecret, constants.EnvData.ADMIN_SECRET)
})
}

View File

@ -24,7 +24,7 @@ func deleteUserTest(s TestSetup, t *testing.T) {
}) })
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET) req.Header.Add("x-authorizer-admin-secret", constants.EnvData.ADMIN_SECRET)
_, err = resolvers.DeleteUser(ctx, model.DeleteUserInput{ _, err = resolvers.DeleteUser(ctx, model.DeleteUserInput{
Email: email, Email: email,
}) })

View File

@ -8,18 +8,18 @@ import (
) )
func TestEnvs(t *testing.T) { func TestEnvs(t *testing.T) {
constants.ENV_PATH = "../../.env.sample" constants.EnvData.ENV_PATH = "../../.env.sample"
assert.Equal(t, constants.ADMIN_SECRET, "admin") assert.Equal(t, constants.EnvData.ADMIN_SECRET, "admin")
assert.Equal(t, constants.ENV, "production") assert.Equal(t, constants.EnvData.ENV, "production")
assert.False(t, constants.DISABLE_EMAIL_VERIFICATION) assert.False(t, constants.EnvData.DISABLE_EMAIL_VERIFICATION)
assert.False(t, constants.DISABLE_MAGIC_LINK_LOGIN) assert.False(t, constants.EnvData.DISABLE_MAGIC_LINK_LOGIN)
assert.False(t, constants.DISABLE_BASIC_AUTHENTICATION) assert.False(t, constants.EnvData.DISABLE_BASIC_AUTHENTICATION)
assert.Equal(t, constants.JWT_TYPE, "HS256") assert.Equal(t, constants.EnvData.JWT_TYPE, "HS256")
assert.Equal(t, constants.JWT_SECRET, "random_string") assert.Equal(t, constants.EnvData.JWT_SECRET, "random_string")
assert.Equal(t, constants.JWT_ROLE_CLAIM, "role") assert.Equal(t, constants.EnvData.JWT_ROLE_CLAIM, "role")
assert.EqualValues(t, constants.ROLES, []string{"user"}) assert.EqualValues(t, constants.EnvData.ROLES, []string{"user"})
assert.EqualValues(t, constants.DEFAULT_ROLES, []string{"user"}) assert.EqualValues(t, constants.EnvData.DEFAULT_ROLES, []string{"user"})
assert.EqualValues(t, constants.PROTECTED_ROLES, []string{"admin"}) assert.EqualValues(t, constants.EnvData.PROTECTED_ROLES, []string{"admin"})
assert.EqualValues(t, constants.ALLOWED_ORIGINS, []string{"*"}) assert.EqualValues(t, constants.EnvData.ALLOWED_ORIGINS, []string{"*"})
} }

View File

@ -2,6 +2,7 @@ package test
import ( import (
"context" "context"
"log"
"testing" "testing"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
@ -12,6 +13,7 @@ func metaTests(s TestSetup, t *testing.T) {
t.Run(`should get meta information`, func(t *testing.T) { t.Run(`should get meta information`, func(t *testing.T) {
ctx := context.Background() ctx := context.Background()
meta, err := resolvers.Meta(ctx) meta, err := resolvers.Meta(ctx)
log.Println("=> meta:", meta)
assert.Nil(t, err) assert.Nil(t, err)
assert.False(t, meta.IsFacebookLoginEnabled) assert.False(t, meta.IsFacebookLoginEnabled)
assert.False(t, meta.IsGoogleLoginEnabled) assert.False(t, meta.IsGoogleLoginEnabled)

View File

@ -6,19 +6,21 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum" "github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/env"
) )
func TestResolvers(t *testing.T) { func TestResolvers(t *testing.T) {
databases := map[string]string{ databases := map[string]string{
enum.Sqlite.String(): "../../data.db", enum.Sqlite.String(): "../../data.db",
enum.Arangodb.String(): "http://root:root@localhost:8529", // enum.Arangodb.String(): "http://root:root@localhost:8529",
enum.Mongodb.String(): "mongodb://localhost:27017", // enum.Mongodb.String(): "mongodb://localhost:27017",
} }
for dbType, dbURL := range databases { for dbType, dbURL := range databases {
constants.DATABASE_URL = dbURL constants.EnvData.DATABASE_URL = dbURL
constants.DATABASE_TYPE = dbType constants.EnvData.DATABASE_TYPE = dbType
db.InitDB() db.InitDB()
env.PersistEnv()
s := testSetup() s := testSetup()
defer s.Server.Close() defer s.Server.Close()
@ -42,6 +44,10 @@ func TestResolvers(t *testing.T) {
usersTest(s, t) usersTest(s, t)
deleteUserTest(s, t) deleteUserTest(s, t)
updateUserTest(s, t) updateUserTest(s, t)
adminLoginTests(s, t)
adminSessionTests(s, t)
updateConfigTests(s, t)
configTests(s, t)
}) })
} }
} }

View File

@ -70,7 +70,8 @@ func testSetup() TestSetup {
Password: "test", Password: "test",
} }
constants.ENV_PATH = "../../.env.sample" constants.EnvData.ENV_PATH = "../../.env.sample"
env.InitEnv() env.InitEnv()
session.InitSession() session.InitSession()

View File

@ -0,0 +1,42 @@
package test
import (
"log"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func updateConfigTests(s TestSetup, t *testing.T) {
t.Run(`should update configs`, func(t *testing.T) {
req, ctx := createContext(s)
originalAppURL := constants.EnvData.APP_URL
log.Println("=> originalAppURL:", constants.EnvData.APP_URL)
data := model.UpdateConfigInput{}
_, err := resolvers.UpdateConfigResolver(ctx, data)
log.Println("error:", err)
assert.NotNil(t, err)
h, _ := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
req.Header.Add("Authorization", "Bearer "+h)
newURL := "https://test.com"
data = model.UpdateConfigInput{
AppURL: &newURL,
}
_, err = resolvers.UpdateConfigResolver(ctx, data)
log.Println("error:", err)
assert.Nil(t, err)
assert.Equal(t, constants.EnvData.APP_URL, newURL)
assert.NotEqual(t, constants.EnvData.APP_URL, originalAppURL)
data = model.UpdateConfigInput{
AppURL: &originalAppURL,
}
_, err = resolvers.UpdateConfigResolver(ctx, data)
assert.Nil(t, err)
})
}

View File

@ -29,7 +29,7 @@ func updateUserTest(s TestSetup, t *testing.T) {
}) })
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET) req.Header.Add("x-authorizer-admin-secret", constants.EnvData.ADMIN_SECRET)
_, err = resolvers.UpdateUser(ctx, model.UpdateUserInput{ _, err = resolvers.UpdateUser(ctx, model.UpdateUserInput{
ID: user.ID, ID: user.ID,
Roles: newRoles, Roles: newRoles,

View File

@ -22,7 +22,7 @@ func usersTest(s TestSetup, t *testing.T) {
users, err := resolvers.Users(ctx) users, err := resolvers.Users(ctx)
assert.NotNil(t, err, "unauthorized") assert.NotNil(t, err, "unauthorized")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET) req.Header.Add("x-authorizer-admin-secret", constants.EnvData.ADMIN_SECRET)
users, err = resolvers.Users(ctx) users, err = resolvers.Users(ctx)
assert.Nil(t, err) assert.Nil(t, err)
rLen := len(users) rLen := len(users)

View File

@ -22,7 +22,7 @@ func TestIsValidEmail(t *testing.T) {
func TestIsValidOrigin(t *testing.T) { func TestIsValidOrigin(t *testing.T) {
// don't use portocal(http/https) for ALLOWED_ORIGINS while testing, // don't use portocal(http/https) for ALLOWED_ORIGINS while testing,
// as we trim them off while running the main function // as we trim them off while running the main function
constants.ALLOWED_ORIGINS = []string{"localhost:8080", "*.google.com", "*.google.in", "*abc.*"} constants.EnvData.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://myapp.com"), "it should be invalid origin")
assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin") assert.False(t, utils.IsValidOrigin("http://appgoogle.com"), "it should be invalid origin")

View File

@ -23,7 +23,7 @@ func verificationRequestsTest(s TestSetup, t *testing.T) {
requests, err := resolvers.VerificationRequests(ctx) requests, err := resolvers.VerificationRequests(ctx)
assert.NotNil(t, err, "unauthorizer") assert.NotNil(t, err, "unauthorizer")
req.Header.Add("x-authorizer-admin-secret", constants.ADMIN_SECRET) req.Header.Add("x-authorizer-admin-secret", constants.EnvData.ADMIN_SECRET)
requests, err = resolvers.VerificationRequests(ctx) requests, err = resolvers.VerificationRequests(ctx)
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -1,50 +1,61 @@
package constants package constants
// this constants are configured via env type EnvConst struct {
var ( ADMIN_SECRET string
ADMIN_SECRET = "" ENV string
ENV = "" ENV_PATH string
ENV_PATH = "" VERSION string
VERSION = "" DATABASE_TYPE string
DATABASE_TYPE = "" DATABASE_URL string
DATABASE_URL = "" DATABASE_NAME string
DATABASE_NAME = "" SMTP_HOST string
SMTP_HOST = "" SMTP_PORT string
SMTP_PORT = "" SENDER_EMAIL string
SENDER_EMAIL = "" SENDER_PASSWORD string
SENDER_PASSWORD = "" JWT_TYPE string
JWT_TYPE = "" JWT_SECRET string
JWT_SECRET = "" ALLOWED_ORIGINS []string
ALLOWED_ORIGINS = []string{} AUTHORIZER_URL string
AUTHORIZER_URL = "" APP_URL string
APP_URL = "" PORT string
PORT = "" REDIS_URL string
REDIS_URL = "" COOKIE_NAME string
IS_PROD = false ADMIN_COOKIE_NAME string
COOKIE_NAME = "" RESET_PASSWORD_URL string
RESET_PASSWORD_URL = "" ENCRYPTION_KEY string `json:"-"`
DISABLE_EMAIL_VERIFICATION = false IS_PROD bool
DISABLE_BASIC_AUTHENTICATION = false DISABLE_EMAIL_VERIFICATION bool
DISABLE_MAGIC_LINK_LOGIN = false DISABLE_BASIC_AUTHENTICATION bool
DISABLE_LOGIN_PAGE = false DISABLE_MAGIC_LINK_LOGIN bool
DISABLE_LOGIN_PAGE bool
// ROLES // ROLES
ROLES = []string{} ROLES []string
PROTECTED_ROLES = []string{} PROTECTED_ROLES []string
DEFAULT_ROLES = []string{} DEFAULT_ROLES []string
JWT_ROLE_CLAIM = "role" JWT_ROLE_CLAIM string
// OAuth login // OAuth login
GOOGLE_CLIENT_ID = "" GOOGLE_CLIENT_ID string
GOOGLE_CLIENT_SECRET = "" GOOGLE_CLIENT_SECRET string
GITHUB_CLIENT_ID = "" GITHUB_CLIENT_ID string
GITHUB_CLIENT_SECRET = "" GITHUB_CLIENT_SECRET string
FACEBOOK_CLIENT_ID = "" FACEBOOK_CLIENT_ID string
FACEBOOK_CLIENT_SECRET = "" FACEBOOK_CLIENT_SECRET string
TWITTER_CLIENT_ID = ""
TWITTER_CLIENT_SECRET = ""
// Org envs // Org envs
ORGANIZATION_NAME = "Authorizer" ORGANIZATION_NAME string
ORGANIZATION_LOGO = "https://authorizer.dev/images/logo.png" ORGANIZATION_LOGO string
) }
var EnvData = EnvConst{
ADMIN_COOKIE_NAME: "authorizer-admin",
JWT_ROLE_CLAIM: "role",
ORGANIZATION_NAME: "Authorizer",
ORGANIZATION_LOGO: "https://authorizer.dev/images/logo.png",
DISABLE_EMAIL_VERIFICATION: false,
DISABLE_BASIC_AUTHENTICATION: false,
DISABLE_MAGIC_LINK_LOGIN: false,
DISABLE_LOGIN_PAGE: false,
IS_PROD: false,
}

View File

@ -17,7 +17,7 @@ import (
func initArangodb() (arangoDriver.Database, error) { func initArangodb() (arangoDriver.Database, error) {
ctx := context.Background() ctx := context.Background()
conn, err := http.NewConnection(http.ConnectionConfig{ conn, err := http.NewConnection(http.ConnectionConfig{
Endpoints: []string{constants.DATABASE_URL}, Endpoints: []string{constants.EnvData.DATABASE_URL},
}) })
if err != nil { if err != nil {
return nil, err return nil, err
@ -32,16 +32,16 @@ func initArangodb() (arangoDriver.Database, error) {
var arangodb driver.Database var arangodb driver.Database
arangodb_exists, err := arangoClient.DatabaseExists(nil, constants.DATABASE_NAME) arangodb_exists, err := arangoClient.DatabaseExists(nil, constants.EnvData.DATABASE_NAME)
if arangodb_exists { if arangodb_exists {
log.Println(constants.DATABASE_NAME + " db exists already") log.Println(constants.EnvData.DATABASE_NAME + " db exists already")
arangodb, err = arangoClient.Database(nil, constants.DATABASE_NAME) arangodb, err = arangoClient.Database(nil, constants.EnvData.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.EnvData.DATABASE_NAME, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -100,5 +100,15 @@ func initArangodb() (arangoDriver.Database, error) {
Sparse: true, Sparse: true,
}) })
configCollectionExists, err := arangodb.CollectionExists(ctx, Collections.Config)
if configCollectionExists {
log.Println(Collections.Config + " collection exists already")
} else {
_, err = arangodb.CreateCollection(ctx, Collections.Config, nil)
if err != nil {
log.Println("error creating collection("+Collections.Config+"):", err)
}
}
return arangodb, err return arangodb, err
} }

161
server/db/config.go Normal file
View File

@ -0,0 +1,161 @@
package db
import (
"fmt"
"log"
"time"
arangoDriver "github.com/arangodb/go-driver"
"github.com/google/uuid"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
type Config struct {
Key string `json:"_key,omitempty" bson:"_key"` // for arangodb
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
Config []byte `gorm:"type:text" json:"config" bson:"config"`
Hash string `gorm:"type:hash" json:"hash" bson:"hash"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
}
// AddConfig function to add config
func (mgr *manager) AddConfig(config Config) (Config, error) {
if config.ID == "" {
config.ID = uuid.New().String()
}
if IsORMSupported {
// copy id as value for fields required for mongodb & arangodb
config.Key = config.ID
result := mgr.sqlDB.Create(&config)
if result.Error != nil {
log.Println("error adding config:", result.Error)
return config, result.Error
}
}
if IsArangoDB {
config.CreatedAt = time.Now().Unix()
config.UpdatedAt = time.Now().Unix()
configCollection, _ := mgr.arangodb.Collection(nil, Collections.Config)
meta, err := configCollection.CreateDocument(arangoDriver.WithOverwrite(nil), config)
if err != nil {
log.Println("error adding config:", err)
return config, err
}
config.Key = meta.Key
config.ID = meta.ID.String()
}
if IsMongoDB {
config.CreatedAt = time.Now().Unix()
config.UpdatedAt = time.Now().Unix()
config.Key = config.ID
configCollection := mgr.mongodb.Collection(Collections.Config, options.Collection())
_, err := configCollection.InsertOne(nil, config)
if err != nil {
log.Println("error adding config:", err)
return config, err
}
}
return config, nil
}
// UpdateConfig function to update config
func (mgr *manager) UpdateConfig(config Config) (Config, error) {
config.UpdatedAt = time.Now().Unix()
if IsORMSupported {
result := mgr.sqlDB.Save(&config)
if result.Error != nil {
log.Println("error updating config:", result.Error)
return config, result.Error
}
}
if IsArangoDB {
collection, _ := mgr.arangodb.Collection(nil, Collections.Config)
meta, err := collection.UpdateDocument(nil, config.Key, config)
if err != nil {
log.Println("error updating config:", err)
return config, err
}
config.Key = meta.Key
config.ID = meta.ID.String()
}
if IsMongoDB {
configCollection := mgr.mongodb.Collection(Collections.Config, options.Collection())
_, err := configCollection.UpdateOne(nil, bson.M{"_id": bson.M{"$eq": config.ID}}, bson.M{"$set": config}, options.MergeUpdateOptions())
if err != nil {
log.Println("error updating config:", err)
return config, err
}
}
return config, nil
}
// GetConfig function to get config
func (mgr *manager) GetConfig() (Config, error) {
var config Config
if IsORMSupported {
result := mgr.sqlDB.First(&config)
if result.Error != nil {
return config, result.Error
}
}
if IsArangoDB {
query := fmt.Sprintf("FOR d in %s RETURN d", Collections.Config)
cursor, err := mgr.arangodb.Query(nil, query, nil)
if err != nil {
return config, err
}
defer cursor.Close()
for {
if !cursor.HasMore() {
if config.Key == "" {
return config, fmt.Errorf("config not found")
}
break
}
_, err := cursor.ReadDocument(nil, &config)
if err != nil {
return config, err
}
}
}
if IsMongoDB {
configCollection := mgr.mongodb.Collection(Collections.Config, options.Collection())
cursor, err := configCollection.Find(nil, bson.M{}, options.Find())
if err != nil {
return config, err
}
defer cursor.Close(nil)
for cursor.Next(nil) {
err := cursor.Decode(&config)
if err != nil {
return config, err
}
}
if config.ID == "" {
return config, fmt.Errorf("config not found")
}
}
return config, nil
}

View File

@ -29,6 +29,9 @@ type Manager interface {
GetVerificationByEmail(email string, identifier string) (VerificationRequest, error) GetVerificationByEmail(email string, identifier string) (VerificationRequest, error)
AddSession(session Session) error AddSession(session Session) error
DeleteUserSession(userId string) error DeleteUserSession(userId string) error
AddConfig(config Config) (Config, error)
UpdateConfig(config Config) (Config, error)
GetConfig() (Config, error)
} }
type manager struct { type manager struct {
@ -42,6 +45,7 @@ type CollectionList struct {
User string User string
VerificationRequest string VerificationRequest string
Session string Session string
Config string
} }
var ( var (
@ -54,6 +58,7 @@ var (
User: Prefix + "users", User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests", VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions", Session: Prefix + "sessions",
Config: Prefix + "config",
} }
) )
@ -61,9 +66,9 @@ func InitDB() {
var sqlDB *gorm.DB var sqlDB *gorm.DB
var err error var err error
IsORMSupported = constants.DATABASE_TYPE != enum.Arangodb.String() && constants.DATABASE_TYPE != enum.Mongodb.String() IsORMSupported = constants.EnvData.DATABASE_TYPE != enum.Arangodb.String() && constants.EnvData.DATABASE_TYPE != enum.Mongodb.String()
IsArangoDB = constants.DATABASE_TYPE == enum.Arangodb.String() IsArangoDB = constants.EnvData.DATABASE_TYPE == enum.Arangodb.String()
IsMongoDB = constants.DATABASE_TYPE == enum.Mongodb.String() IsMongoDB = constants.EnvData.DATABASE_TYPE == enum.Mongodb.String()
// sql db orm config // sql db orm config
ormConfig := &gorm.Config{ ormConfig := &gorm.Config{
@ -72,20 +77,20 @@ func InitDB() {
}, },
} }
log.Println("db type:", constants.DATABASE_TYPE) log.Println("db type:", constants.EnvData.DATABASE_TYPE)
switch constants.DATABASE_TYPE { switch constants.EnvData.DATABASE_TYPE {
case enum.Postgres.String(): case enum.Postgres.String():
sqlDB, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig) sqlDB, err = gorm.Open(postgres.Open(constants.EnvData.DATABASE_URL), ormConfig)
break break
case enum.Sqlite.String(): case enum.Sqlite.String():
sqlDB, err = gorm.Open(sqlite.Open(constants.DATABASE_URL), ormConfig) sqlDB, err = gorm.Open(sqlite.Open(constants.EnvData.DATABASE_URL), ormConfig)
break break
case enum.Mysql.String(): case enum.Mysql.String():
sqlDB, err = gorm.Open(mysql.Open(constants.DATABASE_URL), ormConfig) sqlDB, err = gorm.Open(mysql.Open(constants.EnvData.DATABASE_URL), ormConfig)
break break
case enum.SQLServer.String(): case enum.SQLServer.String():
sqlDB, err = gorm.Open(sqlserver.Open(constants.DATABASE_URL), ormConfig) sqlDB, err = gorm.Open(sqlserver.Open(constants.EnvData.DATABASE_URL), ormConfig)
break break
case enum.Arangodb.String(): case enum.Arangodb.String():
arangodb, err := initArangodb() arangodb, err := initArangodb()
@ -118,7 +123,7 @@ func InitDB() {
if err != nil { if err != nil {
log.Fatal("Failed to init sqlDB:", err) log.Fatal("Failed to init sqlDB:", err)
} else { } else {
sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{}) sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{}, &Config{})
} }
Mgr = &manager{ Mgr = &manager{
sqlDB: sqlDB, sqlDB: sqlDB,

View File

@ -12,7 +12,7 @@ import (
) )
func initMongodb() (*mongo.Database, error) { func initMongodb() (*mongo.Database, error) {
mongodbOptions := options.Client().ApplyURI(constants.DATABASE_URL) mongodbOptions := options.Client().ApplyURI(constants.EnvData.DATABASE_URL)
maxWait := time.Duration(5 * time.Second) maxWait := time.Duration(5 * time.Second)
mongodbOptions.ConnectTimeout = &maxWait mongodbOptions.ConnectTimeout = &maxWait
mongoClient, err := mongo.NewClient(mongodbOptions) mongoClient, err := mongo.NewClient(mongodbOptions)
@ -30,7 +30,7 @@ func initMongodb() (*mongo.Database, error) {
return nil, err return nil, err
} }
mongodb := mongoClient.Database(constants.DATABASE_NAME, options.Database()) mongodb := mongoClient.Database(constants.EnvData.DATABASE_NAME, options.Database())
mongodb.CreateCollection(ctx, Collections.User, options.CreateCollection()) mongodb.CreateCollection(ctx, Collections.User, options.CreateCollection())
userCollection := mongodb.Collection(Collections.User, options.Collection()) userCollection := mongodb.Collection(Collections.User, options.Collection())
@ -73,5 +73,7 @@ func initMongodb() (*mongo.Database, error) {
}, },
}, options.CreateIndexes()) }, options.CreateIndexes())
mongodb.CreateCollection(ctx, Collections.Config, options.CreateCollection())
return mongodb, nil return mongodb, nil
} }

View File

@ -43,7 +43,7 @@ func (mgr *manager) AddUser(user User) (User, error) {
} }
if user.Roles == "" { if user.Roles == "" {
user.Roles = constants.DEFAULT_ROLES[0] user.Roles = constants.EnvData.DEFAULT_ROLES[0]
} }
if IsORMSupported { if IsORMSupported {

View File

@ -27,7 +27,7 @@ type Sender struct {
} }
func NewSender() Sender { func NewSender() Sender {
return Sender{User: constants.SENDER_EMAIL, Password: constants.SENDER_PASSWORD} return Sender{User: constants.EnvData.SENDER_EMAIL, Password: constants.EnvData.SENDER_PASSWORD}
} }
func (sender Sender) SendMail(Dest []string, Subject, bodyMessage string) error { func (sender Sender) SendMail(Dest []string, Subject, bodyMessage string) error {
@ -35,8 +35,8 @@ func (sender Sender) SendMail(Dest []string, Subject, bodyMessage string) error
"To: " + strings.Join(Dest, ",") + "\n" + "To: " + strings.Join(Dest, ",") + "\n" +
"Subject: " + Subject + "\n" + bodyMessage "Subject: " + Subject + "\n" + bodyMessage
err := smtp.SendMail(constants.SMTP_HOST+":"+constants.SMTP_PORT, err := smtp.SendMail(constants.EnvData.SMTP_HOST+":"+constants.EnvData.SMTP_PORT,
smtp.PlainAuth("", sender.User, sender.Password, constants.SMTP_HOST), smtp.PlainAuth("", sender.User, sender.Password, constants.EnvData.SMTP_HOST),
sender.User, Dest, []byte(msg)) sender.User, Dest, []byte(msg))
if err != nil { if err != nil {

227
server/env/env.go vendored
View File

@ -7,6 +7,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/google/uuid"
"github.com/joho/godotenv" "github.com/joho/godotenv"
) )
@ -20,163 +21,169 @@ var (
// InitEnv -> to initialize env and through error if required env are not present // InitEnv -> to initialize env and through error if required env are not present
func InitEnv() { func InitEnv() {
if constants.ENV_PATH == "" { if constants.EnvData.ENV_PATH == "" {
constants.ENV_PATH = `.env` constants.EnvData.ENV_PATH = `.env`
} }
if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" { if ARG_ENV_FILE != nil && *ARG_ENV_FILE != "" {
constants.ENV_PATH = *ARG_ENV_FILE constants.EnvData.ENV_PATH = *ARG_ENV_FILE
} }
err := godotenv.Load(constants.ENV_PATH) err := godotenv.Load(constants.EnvData.ENV_PATH)
if err != nil { if err != nil {
log.Printf("error loading %s file", constants.ENV_PATH) log.Printf("error loading %s file", constants.EnvData.ENV_PATH)
} }
if constants.ADMIN_SECRET == "" { if constants.EnvData.ADMIN_SECRET == "" {
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET") constants.EnvData.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
if constants.ADMIN_SECRET == "" {
panic("root admin secret is required")
}
} }
if constants.ENV == "" { if constants.EnvData.DATABASE_TYPE == "" {
constants.ENV = os.Getenv("ENV") constants.EnvData.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
if constants.ENV == "" { log.Println(constants.EnvData.DATABASE_TYPE)
constants.ENV = "production"
}
if constants.ENV == "production" {
constants.IS_PROD = true
os.Setenv("GIN_MODE", "release")
} else {
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 != "" { if ARG_DB_TYPE != nil && *ARG_DB_TYPE != "" {
constants.DATABASE_TYPE = *ARG_DB_TYPE constants.EnvData.DATABASE_TYPE = *ARG_DB_TYPE
} }
if constants.DATABASE_TYPE == "" { if constants.EnvData.DATABASE_TYPE == "" {
panic("DATABASE_TYPE is required") panic("DATABASE_TYPE is required")
} }
} }
if constants.DATABASE_URL == "" { if constants.EnvData.DATABASE_URL == "" {
constants.DATABASE_URL = os.Getenv("DATABASE_URL") constants.EnvData.DATABASE_URL = os.Getenv("DATABASE_URL")
if ARG_DB_URL != nil && *ARG_DB_URL != "" { if ARG_DB_URL != nil && *ARG_DB_URL != "" {
constants.DATABASE_URL = *ARG_DB_URL constants.EnvData.DATABASE_URL = *ARG_DB_URL
} }
if constants.DATABASE_URL == "" { if constants.EnvData.DATABASE_URL == "" {
panic("DATABASE_URL is required") panic("DATABASE_URL is required")
} }
} }
if constants.DATABASE_NAME == "" { if constants.EnvData.DATABASE_NAME == "" {
constants.DATABASE_NAME = os.Getenv("DATABASE_NAME") constants.EnvData.DATABASE_NAME = os.Getenv("DATABASE_NAME")
if constants.DATABASE_NAME == "" { if constants.EnvData.DATABASE_NAME == "" {
constants.DATABASE_NAME = "authorizer" constants.EnvData.DATABASE_NAME = "authorizer"
} }
} }
if constants.SMTP_HOST == "" { if constants.EnvData.ENV == "" {
constants.SMTP_HOST = os.Getenv("SMTP_HOST") constants.EnvData.ENV = os.Getenv("ENV")
} if constants.EnvData.ENV == "" {
constants.EnvData.ENV = "production"
}
if constants.SMTP_PORT == "" { if constants.EnvData.ENV == "production" {
constants.SMTP_PORT = os.Getenv("SMTP_PORT") constants.EnvData.IS_PROD = true
} os.Setenv("GIN_MODE", "release")
} else {
if constants.SENDER_EMAIL == "" { constants.EnvData.IS_PROD = false
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 == "" { if constants.EnvData.SMTP_HOST == "" {
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/") constants.EnvData.SMTP_HOST = os.Getenv("SMTP_HOST")
}
if constants.EnvData.SMTP_PORT == "" {
constants.EnvData.SMTP_PORT = os.Getenv("SMTP_PORT")
}
if constants.EnvData.SENDER_EMAIL == "" {
constants.EnvData.SENDER_EMAIL = os.Getenv("SENDER_EMAIL")
}
if constants.EnvData.SENDER_PASSWORD == "" {
constants.EnvData.SENDER_PASSWORD = os.Getenv("SENDER_PASSWORD")
}
if constants.EnvData.JWT_SECRET == "" {
constants.EnvData.JWT_SECRET = os.Getenv("JWT_SECRET")
if constants.EnvData.JWT_SECRET == "" {
constants.EnvData.JWT_SECRET = uuid.New().String()
}
}
if constants.EnvData.JWT_TYPE == "" {
constants.EnvData.JWT_TYPE = os.Getenv("JWT_TYPE")
if constants.EnvData.JWT_TYPE == "" {
constants.EnvData.JWT_TYPE = "HS256"
}
}
if constants.EnvData.JWT_ROLE_CLAIM == "" {
constants.EnvData.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
if constants.EnvData.JWT_ROLE_CLAIM == "" {
constants.EnvData.JWT_ROLE_CLAIM = "role"
}
}
if constants.EnvData.AUTHORIZER_URL == "" {
constants.EnvData.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
if ARG_AUTHORIZER_URL != nil && *ARG_AUTHORIZER_URL != "" { if ARG_AUTHORIZER_URL != nil && *ARG_AUTHORIZER_URL != "" {
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL constants.EnvData.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
} }
} }
if constants.PORT == "" { if constants.EnvData.PORT == "" {
constants.PORT = os.Getenv("PORT") constants.EnvData.PORT = os.Getenv("PORT")
if constants.PORT == "" { if constants.EnvData.PORT == "" {
constants.PORT = "8080" constants.EnvData.PORT = "8080"
} }
} }
if constants.REDIS_URL == "" { if constants.EnvData.REDIS_URL == "" {
constants.REDIS_URL = os.Getenv("REDIS_URL") constants.EnvData.REDIS_URL = os.Getenv("REDIS_URL")
} }
if constants.COOKIE_NAME == "" { if constants.EnvData.COOKIE_NAME == "" {
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME") constants.EnvData.COOKIE_NAME = os.Getenv("COOKIE_NAME")
if constants.EnvData.COOKIE_NAME == "" {
constants.EnvData.COOKIE_NAME = "authorizer"
}
} }
if constants.GOOGLE_CLIENT_ID == "" { if constants.EnvData.GOOGLE_CLIENT_ID == "" {
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID") constants.EnvData.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
} }
if constants.GOOGLE_CLIENT_SECRET == "" { if constants.EnvData.GOOGLE_CLIENT_SECRET == "" {
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET") constants.EnvData.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
} }
if constants.GITHUB_CLIENT_ID == "" { if constants.EnvData.GITHUB_CLIENT_ID == "" {
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID") constants.EnvData.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
} }
if constants.GITHUB_CLIENT_SECRET == "" { if constants.EnvData.GITHUB_CLIENT_SECRET == "" {
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET") constants.EnvData.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
} }
if constants.FACEBOOK_CLIENT_ID == "" { if constants.EnvData.FACEBOOK_CLIENT_ID == "" {
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID") constants.EnvData.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
} }
if constants.FACEBOOK_CLIENT_SECRET == "" { if constants.EnvData.FACEBOOK_CLIENT_SECRET == "" {
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET") constants.EnvData.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
} }
if constants.RESET_PASSWORD_URL == "" { if constants.EnvData.RESET_PASSWORD_URL == "" {
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/") constants.EnvData.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
} }
constants.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true" constants.EnvData.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
constants.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true" constants.EnvData.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
constants.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true" constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
constants.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true" constants.EnvData.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true"
if constants.SMTP_HOST == "" || constants.SENDER_EMAIL == "" || constants.SENDER_PASSWORD == "" { if constants.EnvData.SMTP_HOST == "" || constants.EnvData.SENDER_EMAIL == "" || constants.EnvData.SENDER_PASSWORD == "" {
constants.DISABLE_EMAIL_VERIFICATION = true constants.EnvData.DISABLE_EMAIL_VERIFICATION = true
constants.DISABLE_MAGIC_LINK_LOGIN = true constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true
} }
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",") allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
@ -205,18 +212,10 @@ func InitEnv() {
allowedOrigins = []string{"*"} allowedOrigins = []string{"*"}
} }
constants.ALLOWED_ORIGINS = allowedOrigins constants.EnvData.ALLOWED_ORIGINS = allowedOrigins
if constants.JWT_TYPE == "" { if constants.EnvData.DISABLE_EMAIL_VERIFICATION {
constants.JWT_TYPE = "HS256" constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true
}
if constants.COOKIE_NAME == "" {
constants.COOKIE_NAME = "authorizer"
}
if constants.DISABLE_EMAIL_VERIFICATION {
constants.DISABLE_MAGIC_LINK_LOGIN = true
} }
rolesEnv := strings.TrimSpace(os.Getenv("ROLES")) rolesEnv := strings.TrimSpace(os.Getenv("ROLES"))
@ -256,19 +255,19 @@ func InitEnv() {
} }
} }
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRoleSplit) > 0 { if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 {
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`) panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)
} }
constants.ROLES = roles constants.EnvData.ROLES = roles
constants.DEFAULT_ROLES = defaultRoles constants.EnvData.DEFAULT_ROLES = defaultRoles
constants.PROTECTED_ROLES = protectedRoles constants.EnvData.PROTECTED_ROLES = protectedRoles
if os.Getenv("ORGANIZATION_NAME") != "" { if os.Getenv("ORGANIZATION_NAME") != "" {
constants.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME") constants.EnvData.ORGANIZATION_NAME = os.Getenv("ORGANIZATION_NAME")
} }
if os.Getenv("ORGANIZATION_LOGO") != "" { if os.Getenv("ORGANIZATION_LOGO") != "" {
constants.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO") constants.EnvData.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO")
} }
} }

152
server/env/persist_env.go vendored Normal file
View File

@ -0,0 +1,152 @@
package env
import (
"encoding/json"
"log"
"os"
"reflect"
"strings"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/google/uuid"
)
func PersistEnv() error {
config, err := db.Mgr.GetConfig()
// config not found in db
if err != nil {
// AES encryption needs 32 bit key only, so we chop off last 4 characters from 36 bit uuid
hash := uuid.New().String()[:36-4]
constants.EnvData.ENCRYPTION_KEY = hash
encodedHash := utils.EncryptB64(hash)
configData, err := json.Marshal(constants.EnvData)
if err != nil {
return err
}
encryptedConfig, err := utils.EncryptAES(configData)
if err != nil {
return err
}
config = db.Config{
Hash: encodedHash,
Config: encryptedConfig,
}
db.Mgr.AddConfig(config)
} else {
// decrypt the config data from db
// decryption can be done using the hash stored in db
encryptionKey := config.Hash
decryptedEncryptionKey, err := utils.DecryptB64(encryptionKey)
if err != nil {
return err
}
constants.EnvData.ENCRYPTION_KEY = decryptedEncryptionKey
decryptedConfigs, err := utils.DecryptAES(config.Config)
if err != nil {
return err
}
// temp json to validate with env
var jsonData map[string]interface{}
err = json.Unmarshal(decryptedConfigs, &jsonData)
if err != nil {
return err
}
// if env is changed via env file or OS env
// give that higher preference and update db, but we don't recommend it
hasChanged := false
for key, value := range jsonData {
fieldType := reflect.TypeOf(value).String()
// check only for derivative keys
// No need to check for ENCRYPTION_KEY which special key we use for encrypting config data
// as we have removed it from json
envValue := strings.TrimSpace(os.Getenv(key))
// env is not empty
if envValue != "" {
// check the type
// currently we have 3 types of env vars: string, bool, []string{}
if fieldType == "string" {
if value != envValue {
jsonData[key] = envValue
hasChanged = true
}
}
if fieldType == "bool" {
newValue := envValue == "true"
if value != newValue {
jsonData[key] = newValue
hasChanged = true
}
}
if fieldType == "[]interface {}" {
stringArr := []string{}
envStringArr := strings.Split(envValue, ",")
for _, v := range value.([]interface{}) {
stringArr = append(stringArr, v.(string))
}
if !utils.IsStringArrayEqual(stringArr, envStringArr) {
jsonData[key] = envStringArr
}
}
}
}
// handle derivative cases like disabling email verification & magic login
// in case SMTP is off but env is set to true
if jsonData["SMTP_HOST"] == "" || jsonData["SENDER_EMAIL"] == "" || jsonData["SENDER_PASSWORD"] == "" {
if !jsonData["DISABLE_EMAIL_VERIFICATION"].(bool) {
jsonData["DISABLE_EMAIL_VERIFICATION"] = true
hasChanged = true
}
if !jsonData["DISABLE_MAGIC_LINK_LOGIN"].(bool) {
jsonData["DISABLE_MAGIC_LINK_LOGIN"] = true
hasChanged = true
}
}
if hasChanged {
jsonBytes, err := json.Marshal(jsonData)
if err != nil {
return err
}
err = json.Unmarshal(jsonBytes, &constants.EnvData)
if err != nil {
return err
}
configData, err := json.Marshal(constants.EnvData)
if err != nil {
return err
}
encryptedConfig, err := utils.EncryptAES(configData)
if err != nil {
return err
}
config.Config = encryptedConfig
_, err = db.Mgr.UpdateConfig(config)
if err != nil {
log.Println("error updating config:", err)
return err
}
}
}
return nil
}

View File

@ -20,7 +20,6 @@ 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
github.com/ugorji/go v1.2.6 // indirect github.com/ugorji/go v1.2.6 // indirect
github.com/vektah/gqlparser/v2 v2.2.0 github.com/vektah/gqlparser/v2 v2.2.0
go.mongodb.org/mongo-driver v1.8.1 go.mongodb.org/mongo-driver v1.8.1

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,15 @@
package model package model
type AdminLoginInput struct {
AdminSecret string `json:"admin_secret"`
}
type AdminLoginResponse struct {
Message string `json:"message"`
AccessToken string `json:"access_token"`
}
type AuthResponse struct { type AuthResponse struct {
Message string `json:"message"` Message string `json:"message"`
AccessToken *string `json:"access_token"` AccessToken *string `json:"access_token"`
@ -9,6 +18,41 @@ type AuthResponse struct {
User *User `json:"user"` User *User `json:"user"`
} }
type Config struct {
AdminSecret *string `json:"ADMIN_SECRET"`
DatabaseType *string `json:"DATABASE_TYPE"`
DatabaseURL *string `json:"DATABASE_URL"`
DatabaseName *string `json:"DATABASE_NAME"`
SMTPHost *string `json:"SMTP_HOST"`
SMTPPort *string `json:"SMTP_PORT"`
SenderEmail *string `json:"SENDER_EMAIL"`
SenderPassword *string `json:"SENDER_PASSWORD"`
JwtType *string `json:"JWT_TYPE"`
JwtSecret *string `json:"JWT_SECRET"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AuthorizerURL *string `json:"AUTHORIZER_URL"`
AppURL *string `json:"APP_URL"`
RedisURL *string `json:"REDIS_URL"`
CookieName *string `json:"COOKIE_NAME"`
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
Roles []string `json:"ROLES"`
ProtectedRoles []string `json:"PROTECTED_ROLES"`
DefaultRoles []string `json:"DEFAULT_ROLES"`
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}
type DeleteUserInput struct { type DeleteUserInput struct {
Email string `json:"email"` Email string `json:"email"`
} }
@ -73,6 +117,41 @@ type SignUpInput struct {
Roles []string `json:"roles"` Roles []string `json:"roles"`
} }
type UpdateConfigInput struct {
AdminSecret *string `json:"ADMIN_SECRET"`
DatabaseType *string `json:"DATABASE_TYPE"`
DatabaseURL *string `json:"DATABASE_URL"`
DatabaseName *string `json:"DATABASE_NAME"`
SMTPHost *string `json:"SMTP_HOST"`
SMTPPort *string `json:"SMTP_PORT"`
SenderEmail *string `json:"SENDER_EMAIL"`
SenderPassword *string `json:"SENDER_PASSWORD"`
JwtType *string `json:"JWT_TYPE"`
JwtSecret *string `json:"JWT_SECRET"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AuthorizerURL *string `json:"AUTHORIZER_URL"`
AppURL *string `json:"APP_URL"`
RedisURL *string `json:"REDIS_URL"`
CookieName *string `json:"COOKIE_NAME"`
ResetPasswordURL *string `json:"RESET_PASSWORD_URL"`
DisableEmailVerification *bool `json:"DISABLE_EMAIL_VERIFICATION"`
DisableBasicAuthentication *bool `json:"DISABLE_BASIC_AUTHENTICATION"`
DisableMagicLinkLogin *bool `json:"DISABLE_MAGIC_LINK_LOGIN"`
DisableLoginPage *bool `json:"DISABLE_LOGIN_PAGE"`
Roles []string `json:"ROLES"`
ProtectedRoles []string `json:"PROTECTED_ROLES"`
DefaultRoles []string `json:"DEFAULT_ROLES"`
JwtRoleClaim *string `json:"JWT_ROLE_CLAIM"`
GoogleClientID *string `json:"GOOGLE_CLIENT_ID"`
GoogleClientSecret *string `json:"GOOGLE_CLIENT_SECRET"`
GithubClientID *string `json:"GITHUB_CLIENT_ID"`
GithubClientSecret *string `json:"GITHUB_CLIENT_SECRET"`
FacebookClientID *string `json:"FACEBOOK_CLIENT_ID"`
FacebookClientSecret *string `json:"FACEBOOK_CLIENT_SECRET"`
OrganizationName *string `json:"ORGANIZATION_NAME"`
OrganizationLogo *string `json:"ORGANIZATION_LOGO"`
}
type UpdateProfileInput struct { type UpdateProfileInput struct {
OldPassword *string `json:"old_password"` OldPassword *string `json:"old_password"`
NewPassword *string `json:"new_password"` NewPassword *string `json:"new_password"`

View File

@ -62,6 +62,85 @@ type Response {
message: String! message: String!
} }
type AdminLoginResponse {
message: String!
access_token: String!
}
type Config {
ADMIN_SECRET: String
DATABASE_TYPE: String
DATABASE_URL: String
DATABASE_NAME: String
SMTP_HOST: String
SMTP_PORT: String
SENDER_EMAIL: String
SENDER_PASSWORD: String
JWT_TYPE: String
JWT_SECRET: String
ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String
REDIS_URL: String
COOKIE_NAME: String
RESET_PASSWORD_URL: String
DISABLE_EMAIL_VERIFICATION: Boolean
DISABLE_BASIC_AUTHENTICATION: Boolean
DISABLE_MAGIC_LINK_LOGIN: Boolean
DISABLE_LOGIN_PAGE: Boolean
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
JWT_ROLE_CLAIM: String
GOOGLE_CLIENT_ID: String
GOOGLE_CLIENT_SECRET: String
GITHUB_CLIENT_ID: String
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
input UpdateConfigInput {
ADMIN_SECRET: String
DATABASE_TYPE: String
DATABASE_URL: String
DATABASE_NAME: String
SMTP_HOST: String
SMTP_PORT: String
SENDER_EMAIL: String
SENDER_PASSWORD: String
JWT_TYPE: String
JWT_SECRET: String
ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String
REDIS_URL: String
COOKIE_NAME: String
RESET_PASSWORD_URL: String
DISABLE_EMAIL_VERIFICATION: Boolean
DISABLE_BASIC_AUTHENTICATION: Boolean
DISABLE_MAGIC_LINK_LOGIN: Boolean
DISABLE_LOGIN_PAGE: Boolean
ROLES: [String!]
PROTECTED_ROLES: [String!]
DEFAULT_ROLES: [String!]
JWT_ROLE_CLAIM: String
GOOGLE_CLIENT_ID: String
GOOGLE_CLIENT_SECRET: String
GITHUB_CLIENT_ID: String
GITHUB_CLIENT_SECRET: String
FACEBOOK_CLIENT_ID: String
FACEBOOK_CLIENT_SECRET: String
ORGANIZATION_NAME: String
ORGANIZATION_LOGO: String
}
input AdminLoginInput {
admin_secret: String!
}
input SignUpInput { input SignUpInput {
email: String! email: String!
given_name: String given_name: String
@ -153,13 +232,17 @@ type Mutation {
# admin only apis # admin only apis
_delete_user(params: DeleteUserInput!): Response! _delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User! _update_user(params: UpdateUserInput!): User!
_admin_login(params: AdminLoginInput!): AdminLoginResponse!
_update_config(params: UpdateConfigInput!): Response!
} }
type Query { type Query {
meta: Meta! meta: Meta!
session(roles: [String!]): AuthResponse session(roles: [String!]): AuthResponse!
profile: User! profile: User!
# admin only apis # admin only apis
_users: [User!]! _users: [User!]!
_verification_requests: [VerificationRequest!]! _verification_requests: [VerificationRequest!]!
_admin_session: AdminLoginResponse!
_config: Config!
} }

View File

@ -55,6 +55,14 @@ func (r *mutationResolver) UpdateUser(ctx context.Context, params model.UpdateUs
return resolvers.UpdateUser(ctx, params) return resolvers.UpdateUser(ctx, params)
} }
func (r *mutationResolver) AdminLogin(ctx context.Context, params model.AdminLoginInput) (*model.AdminLoginResponse, error) {
return resolvers.AdminLoginResolver(ctx, params)
}
func (r *mutationResolver) UpdateConfig(ctx context.Context, params model.UpdateConfigInput) (*model.Response, error) {
return resolvers.UpdateConfigResolver(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)
} }
@ -75,6 +83,14 @@ func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.Veri
return resolvers.VerificationRequests(ctx) return resolvers.VerificationRequests(ctx)
} }
func (r *queryResolver) AdminSession(ctx context.Context) (*model.AdminLoginResponse, error) {
return resolvers.AdminSession(ctx)
}
func (r *queryResolver) Config(ctx context.Context) (*model.Config, error) {
return resolvers.ConfigResolver(ctx)
}
// Mutation returns generated.MutationResolver implementation. // Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} } func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }

View File

@ -1,7 +1,6 @@
package handlers package handlers
import ( import (
"encoding/base64"
"encoding/json" "encoding/json"
"log" "log"
"net/http" "net/http"
@ -30,17 +29,17 @@ func AppHandler() gin.HandlerFunc {
// return // return
// } // }
stateObj.AuthorizerURL = constants.AUTHORIZER_URL stateObj.AuthorizerURL = constants.EnvData.AUTHORIZER_URL
stateObj.RedirectURL = constants.AUTHORIZER_URL + "/app" stateObj.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/app"
} else { } else {
decodedState, err := base64.StdEncoding.DecodeString(state) decodedState, err := utils.DecryptB64(state)
if err != nil { if err != nil {
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"}) c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
return return
} }
err = json.Unmarshal(decodedState, &stateObj) err = json.Unmarshal([]byte(decodedState), &stateObj)
if err != nil { if err != nil {
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"}) c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
return return
@ -60,7 +59,7 @@ func AppHandler() gin.HandlerFunc {
} }
// validate host and domain of authorizer url // validate host and domain of authorizer url
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.AUTHORIZER_URL { if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.EnvData.AUTHORIZER_URL {
c.JSON(400, gin.H{"error": "invalid host url"}) c.JSON(400, gin.H{"error": "invalid host url"})
return return
} }
@ -77,8 +76,8 @@ func AppHandler() gin.HandlerFunc {
"data": map[string]string{ "data": map[string]string{
"authorizerURL": stateObj.AuthorizerURL, "authorizerURL": stateObj.AuthorizerURL,
"redirectURL": stateObj.RedirectURL, "redirectURL": stateObj.RedirectURL,
"organizationName": constants.ORGANIZATION_NAME, "organizationName": constants.EnvData.ORGANIZATION_NAME,
"organizationLogo": constants.ORGANIZATION_LOGO, "organizationLogo": constants.EnvData.ORGANIZATION_LOGO,
}, },
}) })
} }

View File

@ -1,79 +1,23 @@
package handlers package handlers
import ( import (
"encoding/base64"
"encoding/json"
"log"
"net/http" "net/http"
"strings"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
func DashboardHandler() gin.HandlerFunc { func DashboardHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
state := c.Query("state") isOnboardingCompleted := false
var stateObj State if constants.EnvData.ADMIN_SECRET != "" {
isOnboardingCompleted = true
if state == "" {
// cookie, err := utils.GetAuthToken(c)
// if err != nil {
// c.JSON(400, gin.H{"error": "invalid state"})
// return
// }
stateObj.AuthorizerURL = constants.AUTHORIZER_URL
stateObj.RedirectURL = constants.AUTHORIZER_URL + "/app"
} else {
decodedState, err := base64.StdEncoding.DecodeString(state)
if err != nil {
c.JSON(400, gin.H{"error": "[unable to decode state] invalid state"})
return
}
err = json.Unmarshal(decodedState, &stateObj)
if err != nil {
c.JSON(400, gin.H{"error": "[unable to parse state] invalid state"})
return
}
stateObj.AuthorizerURL = strings.TrimSuffix(stateObj.AuthorizerURL, "/")
stateObj.RedirectURL = strings.TrimSuffix(stateObj.RedirectURL, "/")
// validate redirect url with allowed origins
if !utils.IsValidOrigin(stateObj.RedirectURL) {
c.JSON(400, gin.H{"error": "invalid redirect url"})
return
}
if stateObj.AuthorizerURL == "" {
c.JSON(400, gin.H{"error": "invalid authorizer url"})
return
}
// validate host and domain of authorizer url
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != constants.AUTHORIZER_URL {
c.JSON(400, gin.H{"error": "invalid host url"})
return
}
} }
// debug the request state
if pusher := c.Writer.Pusher(); pusher != nil {
// use pusher.Push() to do server push
if err := pusher.Push("/app/build/bundle.js", nil); err != nil {
log.Printf("Failed to push: %v", err)
}
}
c.HTML(http.StatusOK, "dashboard.tmpl", gin.H{ c.HTML(http.StatusOK, "dashboard.tmpl", gin.H{
"data": map[string]string{ "data": map[string]interface{}{
"authorizerURL": stateObj.AuthorizerURL, "isOnboardingCompleted": isOnboardingCompleted,
"redirectURL": stateObj.RedirectURL,
"organizationName": constants.ORGANIZATION_NAME,
"organizationLogo": constants.ORGANIZATION_LOGO,
}, },
}) })
} }

View File

@ -195,7 +195,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// 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 {
if utils.StringSliceContains(constants.PROTECTED_ROLES, ir) { if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ir) {
hasProtectedRole = true hasProtectedRole = true
} }
} }
@ -238,7 +238,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// check if it contains protected unassigned role // check if it contains protected unassigned role
hasProtectedRole := false hasProtectedRole := false
for _, ur := range unasignedRoles { for _, ur := range unasignedRoles {
if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) { if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) {
hasProtectedRole = true hasProtectedRole = true
} }
} }

View File

@ -34,14 +34,14 @@ func OAuthLoginHandler() gin.HandlerFunc {
// use protected roles verification for admin login only. // use protected roles verification for admin login only.
// though if not associated with user, it will be rejected from oauth_callback // though if not associated with user, it will be rejected from oauth_callback
if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), rolesSplit) { if !utils.IsValidRoles(append([]string{}, append(constants.EnvData.ROLES, constants.EnvData.PROTECTED_ROLES...)...), rolesSplit) {
c.JSON(400, gin.H{ c.JSON(400, gin.H{
"error": "invalid role", "error": "invalid role",
}) })
return return
} }
} else { } else {
roles = strings.Join(constants.DEFAULT_ROLES, ",") roles = strings.Join(constants.EnvData.DEFAULT_ROLES, ",")
} }
uuid := uuid.New() uuid := uuid.New()
@ -57,7 +57,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
} }
session.SetSocailLoginState(oauthStateString, enum.Google.String()) session.SetSocailLoginState(oauthStateString, enum.Google.String())
// during the init of OAuthProvider authorizer url might be empty // during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/google" oauth.OAuthProviders.GoogleConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google"
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Github.String(): case enum.Github.String():
@ -66,7 +66,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break break
} }
session.SetSocailLoginState(oauthStateString, enum.Github.String()) session.SetSocailLoginState(oauthStateString, enum.Github.String())
oauth.OAuthProviders.GithubConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/github" oauth.OAuthProviders.GithubConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github"
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Facebook.String(): case enum.Facebook.String():
@ -75,7 +75,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break break
} }
session.SetSocailLoginState(oauthStateString, enum.Facebook.String()) session.SetSocailLoginState(oauthStateString, enum.Facebook.String())
oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.AUTHORIZER_URL + "/oauth_callback/facebook" oauth.OAuthProviders.FacebookConfig.RedirectURL = constants.EnvData.AUTHORIZER_URL + "/oauth_callback/facebook"
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString) url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
default: default:

View File

@ -22,10 +22,12 @@ func main() {
env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path") env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
flag.Parse() flag.Parse()
constants.VERSION = VERSION constants.EnvData.VERSION = VERSION
env.InitEnv() env.InitEnv()
db.InitDB() db.InitDB()
env.PersistEnv()
session.InitSession() session.InitSession()
oauth.InitOAuth() oauth.InitOAuth()
utils.InitServer() utils.InitServer()
@ -35,7 +37,7 @@ func main() {
router.LoadHTMLGlob("templates/*") router.LoadHTMLGlob("templates/*")
// login page app related routes. // login page app related routes.
// if we put them in router file then tests would fail as templates or build path will be different // if we put them in router file then tests would fail as templates or build path will be different
if !constants.DISABLE_LOGIN_PAGE { if !constants.EnvData.DISABLE_LOGIN_PAGE {
app := router.Group("/app") app := router.Group("/app")
{ {
app.Static("/build", "app/build") app.Static("/build", "app/build")
@ -50,5 +52,5 @@ func main() {
app.GET("/", handlers.DashboardHandler()) app.GET("/", handlers.DashboardHandler())
} }
router.Run(":" + constants.PORT) router.Run(":" + constants.EnvData.PORT)
} }

View File

@ -11,10 +11,10 @@ import (
func GinContextToContextMiddleware() gin.HandlerFunc { func GinContextToContextMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
if constants.AUTHORIZER_URL == "" { if constants.EnvData.AUTHORIZER_URL == "" {
url := location.Get(c) url := location.Get(c)
constants.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host constants.EnvData.AUTHORIZER_URL = url.Scheme + "://" + c.Request.Host
log.Println("authorizer url:", constants.AUTHORIZER_URL) log.Println("authorizer url:", constants.EnvData.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

@ -1,7 +1,6 @@
package middlewares package middlewares
import ( import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
@ -9,7 +8,6 @@ import (
func CORSMiddleware() gin.HandlerFunc { func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin") origin := c.Request.Header.Get("Origin")
constants.APP_URL = origin
if utils.IsValidOrigin(origin) { if utils.IsValidOrigin(origin) {
c.Writer.Header().Set("Access-Control-Allow-Origin", origin) c.Writer.Header().Set("Access-Control-Allow-Origin", origin)

View File

@ -28,33 +28,33 @@ var (
func InitOAuth() { func InitOAuth() {
ctx := context.Background() ctx := context.Background()
if constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "" { if constants.EnvData.GOOGLE_CLIENT_ID != "" && constants.EnvData.GOOGLE_CLIENT_SECRET != "" {
p, err := oidc.NewProvider(ctx, "https://accounts.google.com") p, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil { if err != nil {
log.Fatalln("error creating oidc provider for google:", err) log.Fatalln("error creating oidc provider for google:", err)
} }
OIDCProviders.GoogleOIDC = p OIDCProviders.GoogleOIDC = p
OAuthProviders.GoogleConfig = &oauth2.Config{ OAuthProviders.GoogleConfig = &oauth2.Config{
ClientID: constants.GOOGLE_CLIENT_ID, ClientID: constants.EnvData.GOOGLE_CLIENT_ID,
ClientSecret: constants.GOOGLE_CLIENT_SECRET, ClientSecret: constants.EnvData.GOOGLE_CLIENT_SECRET,
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/google", RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google",
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(), Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"}, Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
} }
} }
if constants.GITHUB_CLIENT_ID != "" && constants.GITHUB_CLIENT_SECRET != "" { if constants.EnvData.GITHUB_CLIENT_ID != "" && constants.EnvData.GITHUB_CLIENT_SECRET != "" {
OAuthProviders.GithubConfig = &oauth2.Config{ OAuthProviders.GithubConfig = &oauth2.Config{
ClientID: constants.GITHUB_CLIENT_ID, ClientID: constants.EnvData.GITHUB_CLIENT_ID,
ClientSecret: constants.GITHUB_CLIENT_SECRET, ClientSecret: constants.EnvData.GITHUB_CLIENT_SECRET,
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/github", RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github",
Endpoint: githubOAuth2.Endpoint, Endpoint: githubOAuth2.Endpoint,
} }
} }
if constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "" { if constants.EnvData.FACEBOOK_CLIENT_ID != "" && constants.EnvData.FACEBOOK_CLIENT_SECRET != "" {
OAuthProviders.FacebookConfig = &oauth2.Config{ OAuthProviders.FacebookConfig = &oauth2.Config{
ClientID: constants.FACEBOOK_CLIENT_ID, ClientID: constants.EnvData.FACEBOOK_CLIENT_ID,
ClientSecret: constants.FACEBOOK_CLIENT_SECRET, ClientSecret: constants.EnvData.FACEBOOK_CLIENT_SECRET,
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/facebook", RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/facebook",
Endpoint: facebookOAuth2.Endpoint, Endpoint: facebookOAuth2.Endpoint,
Scopes: []string{"public_profile", "email"}, Scopes: []string{"public_profile", "email"},
} }

View File

@ -0,0 +1,35 @@
package resolvers
import (
"context"
"fmt"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/utils"
)
func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*model.AdminLoginResponse, error) {
gc, err := utils.GinContextFromContext(ctx)
var res *model.AdminLoginResponse
if err != nil {
return res, err
}
if params.AdminSecret != constants.EnvData.ADMIN_SECRET {
return res, fmt.Errorf(`invalid admin secret`)
}
hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
if err != nil {
return res, err
}
utils.SetAdminCookie(gc, hashedKey)
res = &model.AdminLoginResponse{
AccessToken: hashedKey,
Message: "admin logged in successfully",
}
return res, nil
}

View File

@ -0,0 +1,35 @@
package resolvers
import (
"context"
"fmt"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/utils"
)
func AdminSession(ctx context.Context) (*model.AdminLoginResponse, error) {
gc, err := utils.GinContextFromContext(ctx)
var res *model.AdminLoginResponse
if err != nil {
return res, err
}
if !utils.IsSuperAdmin(gc) {
return res, fmt.Errorf("unauthorized")
}
hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
if err != nil {
return res, err
}
utils.SetAdminCookie(gc, hashedKey)
res = &model.AdminLoginResponse{
AccessToken: hashedKey,
Message: "admin logged in successfully",
}
return res, nil
}

View File

@ -0,0 +1,59 @@
package resolvers
import (
"context"
"fmt"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/utils"
)
func ConfigResolver(ctx context.Context) (*model.Config, error) {
gc, err := utils.GinContextFromContext(ctx)
var res *model.Config
if err != nil {
return res, err
}
if !utils.IsSuperAdmin(gc) {
return res, fmt.Errorf("unauthorized")
}
res = &model.Config{
AdminSecret: &constants.EnvData.ADMIN_SECRET,
DatabaseType: &constants.EnvData.DATABASE_TYPE,
DatabaseURL: &constants.EnvData.DATABASE_URL,
DatabaseName: &constants.EnvData.DATABASE_NAME,
SMTPHost: &constants.EnvData.SMTP_HOST,
SMTPPort: &constants.EnvData.SMTP_PORT,
SenderEmail: &constants.EnvData.SENDER_EMAIL,
SenderPassword: &constants.EnvData.SENDER_PASSWORD,
JwtType: &constants.EnvData.JWT_TYPE,
JwtSecret: &constants.EnvData.JWT_SECRET,
AllowedOrigins: constants.EnvData.ALLOWED_ORIGINS,
AuthorizerURL: &constants.EnvData.AUTHORIZER_URL,
AppURL: &constants.EnvData.APP_URL,
RedisURL: &constants.EnvData.REDIS_URL,
CookieName: &constants.EnvData.COOKIE_NAME,
ResetPasswordURL: &constants.EnvData.RESET_PASSWORD_URL,
DisableEmailVerification: &constants.EnvData.DISABLE_EMAIL_VERIFICATION,
DisableBasicAuthentication: &constants.EnvData.DISABLE_BASIC_AUTHENTICATION,
DisableMagicLinkLogin: &constants.EnvData.DISABLE_MAGIC_LINK_LOGIN,
DisableLoginPage: &constants.EnvData.DISABLE_LOGIN_PAGE,
Roles: constants.EnvData.ROLES,
ProtectedRoles: constants.EnvData.PROTECTED_ROLES,
DefaultRoles: constants.EnvData.DEFAULT_ROLES,
JwtRoleClaim: &constants.EnvData.JWT_ROLE_CLAIM,
GoogleClientID: &constants.EnvData.GOOGLE_CLIENT_ID,
GoogleClientSecret: &constants.EnvData.GOOGLE_CLIENT_SECRET,
GithubClientID: &constants.EnvData.GITHUB_CLIENT_ID,
GithubClientSecret: &constants.EnvData.GITHUB_CLIENT_SECRET,
FacebookClientID: &constants.EnvData.FACEBOOK_CLIENT_ID,
FacebookClientSecret: &constants.EnvData.FACEBOOK_CLIENT_SECRET,
OrganizationName: &constants.EnvData.ORGANIZATION_NAME,
OrganizationLogo: &constants.EnvData.ORGANIZATION_LOGO,
}
return res, nil
}

View File

@ -20,7 +20,7 @@ func ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*mod
if err != nil { if err != nil {
return res, err return res, err
} }
if constants.DISABLE_BASIC_AUTHENTICATION { if constants.EnvData.DISABLE_BASIC_AUTHENTICATION {
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
host := gc.Request.Host host := gc.Request.Host

View File

@ -22,7 +22,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
return res, err return res, err
} }
if constants.DISABLE_BASIC_AUTHENTICATION { if constants.EnvData.DISABLE_BASIC_AUTHENTICATION {
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
@ -46,7 +46,7 @@ func Login(ctx context.Context, params model.LoginInput) (*model.AuthResponse, e
log.Println("compare password error:", err) log.Println("compare password error:", err)
return res, fmt.Errorf(`invalid password`) return res, fmt.Errorf(`invalid password`)
} }
roles := constants.DEFAULT_ROLES roles := constants.EnvData.DEFAULT_ROLES
currentRoles := strings.Split(user.Roles, ",") currentRoles := strings.Split(user.Roles, ",")
if len(params.Roles) > 0 { if len(params.Roles) > 0 {
if !utils.IsValidRoles(currentRoles, params.Roles) { if !utils.IsValidRoles(currentRoles, params.Roles) {

View File

@ -17,7 +17,7 @@ import (
func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*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_LINK_LOGIN { if constants.EnvData.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`)
} }
@ -41,13 +41,13 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod
// 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
if !utils.IsValidRoles(constants.ROLES, params.Roles) { if !utils.IsValidRoles(constants.EnvData.ROLES, params.Roles) {
return res, fmt.Errorf(`invalid roles`) return res, fmt.Errorf(`invalid roles`)
} else { } else {
inputRoles = params.Roles inputRoles = params.Roles
} }
} else { } else {
inputRoles = constants.DEFAULT_ROLES inputRoles = constants.EnvData.DEFAULT_ROLES
} }
user.Roles = strings.Join(inputRoles, ",") user.Roles = strings.Join(inputRoles, ",")
@ -72,7 +72,7 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod
// check if it contains protected unassigned role // check if it contains protected unassigned role
hasProtectedRole := false hasProtectedRole := false
for _, ur := range unasignedRoles { for _, ur := range unasignedRoles {
if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) { if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) {
hasProtectedRole = true hasProtectedRole = true
} }
} }
@ -98,7 +98,7 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod
} }
} }
if !constants.DISABLE_EMAIL_VERIFICATION { if !constants.EnvData.DISABLE_EMAIL_VERIFICATION {
// insert verification request // insert verification request
verificationType := enum.MagicLinkLogin.String() verificationType := enum.MagicLinkLogin.String()
token, err := utils.CreateVerificationToken(params.Email, verificationType) token, err := utils.CreateVerificationToken(params.Email, verificationType)

View File

@ -14,7 +14,7 @@ import (
func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) { func ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error) {
var res *model.Response var res *model.Response
if constants.DISABLE_BASIC_AUTHENTICATION { if constants.EnvData.DISABLE_BASIC_AUTHENTICATION {
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }

View File

@ -45,7 +45,7 @@ func Session(ctx context.Context, roles []string) (*model.AuthResponse, error) {
expiresTimeObj := time.Unix(expiresAt, 0) expiresTimeObj := time.Unix(expiresAt, 0)
currentTimeObj := time.Now() currentTimeObj := time.Now()
claimRoleInterface := claim[constants.JWT_ROLE_CLAIM].([]interface{}) claimRoleInterface := claim[constants.EnvData.JWT_ROLE_CLAIM].([]interface{})
claimRoles := make([]string, len(claimRoleInterface)) claimRoles := make([]string, len(claimRoleInterface))
for i, v := range claimRoleInterface { for i, v := range claimRoleInterface {
claimRoles[i] = v.(string) claimRoles[i] = v.(string)

View File

@ -22,7 +22,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
return res, err return res, err
} }
if constants.DISABLE_BASIC_AUTHENTICATION { if constants.EnvData.DISABLE_BASIC_AUTHENTICATION {
return res, fmt.Errorf(`basic authentication is disabled for this instance`) return res, fmt.Errorf(`basic authentication is disabled for this instance`)
} }
if params.ConfirmPassword != params.Password { if params.ConfirmPassword != params.Password {
@ -52,13 +52,13 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
if len(params.Roles) > 0 { if len(params.Roles) > 0 {
// check if roles exists // check if roles exists
if !utils.IsValidRoles(constants.ROLES, params.Roles) { if !utils.IsValidRoles(constants.EnvData.ROLES, params.Roles) {
return res, fmt.Errorf(`invalid roles`) return res, fmt.Errorf(`invalid roles`)
} else { } else {
inputRoles = params.Roles inputRoles = params.Roles
} }
} else { } else {
inputRoles = constants.DEFAULT_ROLES inputRoles = constants.EnvData.DEFAULT_ROLES
} }
user := db.User{ user := db.User{
@ -103,7 +103,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
} }
user.SignupMethods = enum.BasicAuth.String() user.SignupMethods = enum.BasicAuth.String()
if constants.DISABLE_EMAIL_VERIFICATION { if constants.EnvData.DISABLE_EMAIL_VERIFICATION {
now := time.Now().Unix() now := time.Now().Unix()
user.EmailVerifiedAt = &now user.EmailVerifiedAt = &now
} }
@ -115,7 +115,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
roles := strings.Split(user.Roles, ",") roles := strings.Split(user.Roles, ",")
userToReturn := utils.GetResponseUserData(user) userToReturn := utils.GetResponseUserData(user)
if !constants.DISABLE_EMAIL_VERIFICATION { if !constants.EnvData.DISABLE_EMAIL_VERIFICATION {
// insert verification request // insert verification request
verificationType := enum.BasicAuthSignup.String() verificationType := enum.BasicAuthSignup.String()
token, err := utils.CreateVerificationToken(params.Email, verificationType) token, err := utils.CreateVerificationToken(params.Email, verificationType)

View File

@ -0,0 +1,119 @@
package resolvers
import (
"context"
"encoding/json"
"fmt"
"log"
"reflect"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/utils"
)
func UpdateConfigResolver(ctx context.Context, params model.UpdateConfigInput) (*model.Response, error) {
gc, err := utils.GinContextFromContext(ctx)
var res *model.Response
if err != nil {
return res, err
}
if !utils.IsSuperAdmin(gc) {
return res, fmt.Errorf("unauthorized")
}
var data map[string]interface{}
byteData, err := json.Marshal(params)
if err != nil {
return res, fmt.Errorf("error marshalling params: %t", err)
}
err = json.Unmarshal(byteData, &data)
if err != nil {
return res, fmt.Errorf("error un-marshalling params: %t", err)
}
updatedData := make(map[string]interface{})
for key, value := range data {
if value != nil {
fieldType := reflect.TypeOf(value).String()
if fieldType == "string" || fieldType == "bool" {
updatedData[key] = value
}
if fieldType == "[]interface {}" {
stringArr := []string{}
for _, v := range value.([]interface{}) {
stringArr = append(stringArr, v.(string))
}
updatedData[key] = stringArr
}
}
}
// handle derivative cases like disabling email verification & magic login
// in case SMTP is off but env is set to true
if updatedData["SMTP_HOST"] == "" || updatedData["SENDER_EMAIL"] == "" || updatedData["SENDER_PASSWORD"] == "" {
if !updatedData["DISABLE_EMAIL_VERIFICATION"].(bool) {
updatedData["DISABLE_EMAIL_VERIFICATION"] = true
}
if !updatedData["DISABLE_MAGIC_LINK_LOGIN"].(bool) {
updatedData["DISABLE_MAGIC_LINK_LOGIN"] = true
}
}
config, err := db.Mgr.GetConfig()
if err != nil {
return res, err
}
jsonBytes, err := json.Marshal(updatedData)
if err != nil {
return res, err
}
err = json.Unmarshal(jsonBytes, &constants.EnvData)
if err != nil {
return res, err
}
configData, err := json.Marshal(constants.EnvData)
if err != nil {
return res, err
}
encryptedConfig, err := utils.EncryptAES(configData)
if err != nil {
return res, err
}
// in case of db change re-initialize db
if params.DatabaseType != nil || params.DatabaseURL != nil || params.DatabaseName != nil {
db.InitDB()
}
// in case of admin secret change update the cookie with new hash
if params.AdminSecret != nil {
hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
if err != nil {
return res, err
}
utils.SetAdminCookie(gc, hashedKey)
}
config.Config = encryptedConfig
_, err = db.Mgr.UpdateConfig(config)
if err != nil {
log.Println("error updating config:", err)
return res, err
}
res = &model.Response{
Message: "configurations updated successfully",
}
return res, nil
}

View File

@ -112,7 +112,7 @@ func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User,
inputRoles = append(inputRoles, *item) inputRoles = append(inputRoles, *item)
} }
if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), inputRoles) { if !utils.IsValidRoles(append([]string{}, append(constants.EnvData.ROLES, constants.EnvData.PROTECTED_ROLES...)...), inputRoles) {
return res, fmt.Errorf("invalid list of roles") return res, fmt.Errorf("invalid list of roles")
} }

View File

@ -95,9 +95,9 @@ func RemoveSocialLoginState(key string) {
} }
func InitSession() { func InitSession() {
if constants.REDIS_URL != "" { if constants.EnvData.REDIS_URL != "" {
log.Println("using redis store to save sessions") log.Println("using redis store to save sessions")
opt, err := redis.ParseURL(constants.REDIS_URL) opt, err := redis.ParseURL(constants.EnvData.REDIS_URL)
if err != nil { if err != nil {
log.Fatalln("Error parsing redis url:", err) log.Fatalln("Error parsing redis url:", err)
} }

View File

@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
"net/url"
"os" "os"
"strings" "strings"
"time" "time"
@ -14,10 +15,11 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"golang.org/x/crypto/bcrypt"
) )
func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) { func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) {
t := jwt.New(jwt.GetSigningMethod(constants.JWT_TYPE)) t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE))
expiryBound := time.Hour expiryBound := time.Hour
if tokenType == enum.RefreshToken { if tokenType == enum.RefreshToken {
// expires in 1 year // expires in 1 year
@ -32,11 +34,11 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
json.Unmarshal(userBytes, &userMap) 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(),
"allowed_roles": strings.Split(user.Roles, ","), "allowed_roles": strings.Split(user.Roles, ","),
constants.JWT_ROLE_CLAIM: roles, constants.EnvData.JWT_ROLE_CLAIM: roles,
} }
for k, v := range userMap { for k, v := range userMap {
@ -77,7 +79,7 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
t.Claims = customClaims t.Claims = customClaims
token, err := t.SignedString([]byte(constants.JWT_SECRET)) token, err := t.SignedString([]byte(constants.EnvData.JWT_SECRET))
if err != nil { if err != nil {
return "", 0, err return "", 0, err
} }
@ -89,7 +91,6 @@ func GetAuthToken(gc *gin.Context) (string, error) {
token, err := GetCookie(gc) token, err := GetCookie(gc)
if err != nil || token == "" { if err != nil || token == "" {
// try to check in auth header for cookie // try to check in auth header for cookie
log.Println("cookie not found checking headers")
auth := gc.Request.Header.Get("Authorization") auth := gc.Request.Header.Get("Authorization")
if auth == "" { if auth == "" {
return "", fmt.Errorf(`unauthorized`) return "", fmt.Errorf(`unauthorized`)
@ -105,7 +106,7 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
claims := jwt.MapClaims{} claims := jwt.MapClaims{}
_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(constants.JWT_SECRET), nil return []byte(constants.EnvData.JWT_SECRET), nil
}) })
if err != nil { if err != nil {
return res, err return res, err
@ -124,3 +125,36 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
return res, nil return res, nil
} }
func CreateAdminAuthToken(tokenType enum.TokenType, c *gin.Context) (string, error) {
return HashPassword(constants.EnvData.ADMIN_SECRET)
}
func GetAdminAuthToken(gc *gin.Context) (string, error) {
token, err := GetAdminCookie(gc)
if err != nil || token == "" {
// try to check in auth header for cookie
auth := gc.Request.Header.Get("Authorization")
if auth == "" {
return "", fmt.Errorf(`unauthorized`)
}
token = strings.TrimPrefix(auth, "Bearer ")
}
// cookie escapes special characters like $
// hence we need to unescape before comparing
decodedValue, err := url.QueryUnescape(token)
if err != nil {
return "", err
}
err = bcrypt.CompareHashAndPassword([]byte(decodedValue), []byte(constants.EnvData.ADMIN_SECRET))
log.Println("error comparing hash:", err)
if err != nil {
return "", fmt.Errorf(`unauthorized`)
}
return token, nil
}

View File

@ -10,21 +10,21 @@ import (
func SetCookie(gc *gin.Context, token string) { func SetCookie(gc *gin.Context, token string) {
secure := true secure := true
httpOnly := true httpOnly := true
host, _ := GetHostParts(constants.AUTHORIZER_URL) host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
domain := GetDomainName(constants.AUTHORIZER_URL) domain := GetDomainName(constants.EnvData.AUTHORIZER_URL)
if domain != "localhost" { if domain != "localhost" {
domain = "." + domain domain = "." + domain
} }
gc.SetSameSite(http.SameSiteNoneMode) gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(constants.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly) gc.SetCookie(constants.EnvData.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
gc.SetCookie(constants.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly) gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly)
} }
func GetCookie(gc *gin.Context) (string, error) { func GetCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(constants.COOKIE_NAME) cookie, err := gc.Request.Cookie(constants.EnvData.COOKIE_NAME)
if err != nil { if err != nil {
cookie, err = gc.Request.Cookie(constants.COOKIE_NAME + "-client") cookie, err = gc.Request.Cookie(constants.EnvData.COOKIE_NAME + "-client")
if err != nil { if err != nil {
return "", err return "", err
} }
@ -37,13 +37,37 @@ func DeleteCookie(gc *gin.Context) {
secure := true secure := true
httpOnly := true httpOnly := true
host, _ := GetHostParts(constants.AUTHORIZER_URL) host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
domain := GetDomainName(constants.AUTHORIZER_URL) domain := GetDomainName(constants.EnvData.AUTHORIZER_URL)
if domain != "localhost" { if domain != "localhost" {
domain = "." + domain domain = "." + domain
} }
gc.SetSameSite(http.SameSiteNoneMode) gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(constants.COOKIE_NAME, "", -1, "/", host, secure, httpOnly) gc.SetCookie(constants.EnvData.COOKIE_NAME, "", -1, "/", host, secure, httpOnly)
gc.SetCookie(constants.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly) gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly)
}
func SetAdminCookie(gc *gin.Context, token string) {
secure := true
httpOnly := true
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
gc.SetCookie(constants.EnvData.ADMIN_COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
}
func GetAdminCookie(gc *gin.Context) (string, error) {
cookie, err := gc.Request.Cookie(constants.EnvData.ADMIN_COOKIE_NAME)
if err != nil {
return "", err
}
return cookie.Value, nil
}
func DeleteAdminCookie(gc *gin.Context, token string) {
secure := true
httpOnly := true
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
gc.SetCookie(constants.EnvData.ADMIN_COOKIE_NAME, "", -1, "/", host, secure, httpOnly)
} }

83
server/utils/crypto.go Normal file
View File

@ -0,0 +1,83 @@
package utils
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"io"
"github.com/authorizerdev/authorizer/server/constants"
)
func EncryptB64(text string) string {
return base64.StdEncoding.EncodeToString([]byte(text))
}
func DecryptB64(s string) (string, error) {
data, err := base64.StdEncoding.DecodeString(s)
if err != nil {
return "", err
}
return string(data), nil
}
func EncryptAES(text []byte) ([]byte, error) {
key := []byte(constants.EnvData.ENCRYPTION_KEY)
c, err := aes.NewCipher(key)
var res []byte
if err != nil {
return res, err
}
// gcm or Galois/Counter Mode, is a mode of operation
// for symmetric key cryptographic block ciphers
// - https://en.wikipedia.org/wiki/Galois/Counter_Mode
gcm, err := cipher.NewGCM(c)
if err != nil {
return res, err
}
// creates a new byte array the size of the nonce
// which must be passed to Seal
nonce := make([]byte, gcm.NonceSize())
// populates our nonce with a cryptographically secure
// random sequence
if _, err = io.ReadFull(rand.Reader, nonce); err != nil {
return res, err
}
// here we encrypt our text using the Seal function
// Seal encrypts and authenticates plaintext, authenticates the
// additional data and appends the result to dst, returning the updated
// slice. The nonce must be NonceSize() bytes long and unique for all
// time, for a given key.
return gcm.Seal(nonce, nonce, text, nil), nil
}
func DecryptAES(ciphertext []byte) ([]byte, error) {
key := []byte(constants.EnvData.ENCRYPTION_KEY)
c, err := aes.NewCipher(key)
var res []byte
if err != nil {
return res, err
}
gcm, err := cipher.NewGCM(c)
if err != nil {
return res, err
}
nonceSize := gcm.NonceSize()
if len(ciphertext) < nonceSize {
return res, err
}
nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:]
plaintext, err := gcm.Open(nil, nonce, ciphertext, nil)
if err != nil {
return res, err
}
return plaintext, nil
}

View File

@ -100,7 +100,7 @@ func SendVerificationMail(toEmail, token string) error {
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div> <div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
</body> </body>
</html> </html>
`, constants.ORGANIZATION_LOGO, constants.ORGANIZATION_NAME, constants.AUTHORIZER_URL+"/verify_email"+"?token="+token) `, constants.EnvData.ORGANIZATION_LOGO, constants.EnvData.ORGANIZATION_NAME, constants.EnvData.AUTHORIZER_URL+"/verify_email"+"?token="+token)
bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message) bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)
return sender.SendMail(Receiver, Subject, bodyMessage) return sender.SendMail(Receiver, Subject, bodyMessage)
@ -108,8 +108,8 @@ func SendVerificationMail(toEmail, token string) error {
// SendForgotPasswordMail to send verification email // SendForgotPasswordMail to send verification email
func SendForgotPasswordMail(toEmail, token, host string) error { func SendForgotPasswordMail(toEmail, token, host string) error {
if constants.RESET_PASSWORD_URL == "" { if constants.EnvData.RESET_PASSWORD_URL == "" {
constants.RESET_PASSWORD_URL = constants.AUTHORIZER_URL + "/app/reset-password" constants.EnvData.RESET_PASSWORD_URL = constants.EnvData.AUTHORIZER_URL + "/app/reset-password"
} }
sender := email.NewSender() sender := email.NewSender()
@ -204,7 +204,7 @@ func SendForgotPasswordMail(toEmail, token, host string) error {
<div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div> <div style="position: absolute; left: -9999px; top: -9999px; margin: 0px;"></div>
</body> </body>
</html> </html>
`, constants.ORGANIZATION_LOGO, toEmail, constants.RESET_PASSWORD_URL+"?token="+token) `, constants.EnvData.ORGANIZATION_LOGO, toEmail, constants.EnvData.RESET_PASSWORD_URL+"?token="+token)
bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message) bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)

View File

@ -9,12 +9,12 @@ import (
// version, // version,
func GetMetaInfo() model.Meta { func GetMetaInfo() model.Meta {
return model.Meta{ return model.Meta{
Version: constants.VERSION, Version: constants.EnvData.VERSION,
IsGoogleLoginEnabled: constants.GOOGLE_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "", IsGoogleLoginEnabled: constants.EnvData.GOOGLE_CLIENT_ID != "" && constants.EnvData.GOOGLE_CLIENT_SECRET != "",
IsGithubLoginEnabled: constants.GITHUB_CLIENT_ID != "" && constants.GOOGLE_CLIENT_SECRET != "", IsGithubLoginEnabled: constants.EnvData.GITHUB_CLIENT_ID != "" && constants.EnvData.GOOGLE_CLIENT_SECRET != "",
IsFacebookLoginEnabled: constants.FACEBOOK_CLIENT_ID != "" && constants.FACEBOOK_CLIENT_SECRET != "", IsFacebookLoginEnabled: constants.EnvData.FACEBOOK_CLIENT_ID != "" && constants.EnvData.FACEBOOK_CLIENT_SECRET != "",
IsBasicAuthenticationEnabled: !constants.DISABLE_BASIC_AUTHENTICATION, IsBasicAuthenticationEnabled: !constants.EnvData.DISABLE_BASIC_AUTHENTICATION,
IsEmailVerificationEnabled: !constants.DISABLE_EMAIL_VERIFICATION, IsEmailVerificationEnabled: !constants.EnvData.DISABLE_EMAIL_VERIFICATION,
IsMagicLinkLoginEnabled: !constants.DISABLE_MAGIC_LINK_LOGIN, IsMagicLinkLoginEnabled: !constants.EnvData.DISABLE_MAGIC_LINK_LOGIN,
} }
} }

View File

@ -16,7 +16,7 @@ func IsValidEmail(email string) bool {
} }
func IsValidOrigin(url string) bool { func IsValidOrigin(url string) bool {
if len(constants.ALLOWED_ORIGINS) == 1 && constants.ALLOWED_ORIGINS[0] == "*" { if len(constants.EnvData.ALLOWED_ORIGINS) == 1 && constants.EnvData.ALLOWED_ORIGINS[0] == "*" {
return true return true
} }
@ -24,7 +24,7 @@ func IsValidOrigin(url string) bool {
hostName, port := GetHostParts(url) hostName, port := GetHostParts(url)
currentOrigin := hostName + ":" + port currentOrigin := hostName + ":" + port
for _, origin := range constants.ALLOWED_ORIGINS { for _, origin := range constants.EnvData.ALLOWED_ORIGINS {
replacedString := origin replacedString := origin
// if has regex whitelisted domains // if has regex whitelisted domains
if strings.Contains(origin, "*") { if strings.Contains(origin, "*") {
@ -50,12 +50,17 @@ func IsValidOrigin(url string) bool {
} }
func IsSuperAdmin(gc *gin.Context) bool { func IsSuperAdmin(gc *gin.Context) bool {
secret := gc.Request.Header.Get("x-authorizer-admin-secret") token, err := GetAdminAuthToken(gc)
if secret == "" { if err != nil {
return false secret := gc.Request.Header.Get("x-authorizer-admin-secret")
if secret == "" {
return false
}
return secret == constants.EnvData.ADMIN_SECRET
} }
return secret == constants.ADMIN_SECRET return token != ""
} }
func IsValidRoles(userRoles []string, roles []string) bool { func IsValidRoles(userRoles []string, roles []string) bool {

View File

@ -20,23 +20,23 @@ type CustomClaim struct {
} }
func CreateVerificationToken(email string, tokenType string) (string, error) { func CreateVerificationToken(email string, tokenType string) (string, error) {
t := jwt.New(jwt.GetSigningMethod(constants.JWT_TYPE)) t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE))
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,
UserInfo{Email: email, Host: constants.AUTHORIZER_URL, RedirectURL: constants.APP_URL}, UserInfo{Email: email, Host: constants.EnvData.AUTHORIZER_URL, RedirectURL: constants.EnvData.APP_URL},
} }
return t.SignedString([]byte(constants.JWT_SECRET)) return t.SignedString([]byte(constants.EnvData.JWT_SECRET))
} }
func VerifyVerificationToken(token string) (*CustomClaim, error) { func VerifyVerificationToken(token string) (*CustomClaim, error) {
claims := &CustomClaim{} claims := &CustomClaim{}
_, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) { _, err := jwt.ParseWithClaims(token, claims, func(token *jwt.Token) (interface{}, error) {
return []byte(constants.JWT_SECRET), nil return []byte(constants.EnvData.JWT_SECRET), nil
}) })
if err != nil { if err != nil {
return claims, err return claims, err