feat: persist encrypted env

This commit is contained in:
Lakhan Samani 2021-12-31 13:52:10 +05:30
parent d9c40057e6
commit e35d0cbcd6
41 changed files with 751 additions and 298 deletions

View File

@ -24,7 +24,7 @@ func deleteUserTest(s TestSetup, t *testing.T) {
})
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{
Email: email,
})

View File

@ -8,18 +8,18 @@ import (
)
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.ENV, "production")
assert.False(t, constants.DISABLE_EMAIL_VERIFICATION)
assert.False(t, constants.DISABLE_MAGIC_LINK_LOGIN)
assert.False(t, constants.DISABLE_BASIC_AUTHENTICATION)
assert.Equal(t, constants.JWT_TYPE, "HS256")
assert.Equal(t, constants.JWT_SECRET, "random_string")
assert.Equal(t, constants.JWT_ROLE_CLAIM, "role")
assert.EqualValues(t, constants.ROLES, []string{"user"})
assert.EqualValues(t, constants.DEFAULT_ROLES, []string{"user"})
assert.EqualValues(t, constants.PROTECTED_ROLES, []string{"admin"})
assert.EqualValues(t, constants.ALLOWED_ORIGINS, []string{"*"})
assert.Equal(t, constants.EnvData.ADMIN_SECRET, "admin")
assert.Equal(t, constants.EnvData.ENV, "production")
assert.False(t, constants.EnvData.DISABLE_EMAIL_VERIFICATION)
assert.False(t, constants.EnvData.DISABLE_MAGIC_LINK_LOGIN)
assert.False(t, constants.EnvData.DISABLE_BASIC_AUTHENTICATION)
assert.Equal(t, constants.EnvData.JWT_TYPE, "HS256")
assert.Equal(t, constants.EnvData.JWT_SECRET, "random_string")
assert.Equal(t, constants.EnvData.JWT_ROLE_CLAIM, "role")
assert.EqualValues(t, constants.EnvData.ROLES, []string{"user"})
assert.EqualValues(t, constants.EnvData.DEFAULT_ROLES, []string{"user"})
assert.EqualValues(t, constants.EnvData.PROTECTED_ROLES, []string{"admin"})
assert.EqualValues(t, constants.EnvData.ALLOWED_ORIGINS, []string{"*"})
}

View File

@ -16,8 +16,8 @@ func TestResolvers(t *testing.T) {
}
for dbType, dbURL := range databases {
constants.DATABASE_URL = dbURL
constants.DATABASE_TYPE = dbType
constants.EnvData.DATABASE_URL = dbURL
constants.EnvData.DATABASE_TYPE = dbType
db.InitDB()
s := testSetup()

View File

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

View File

@ -29,7 +29,7 @@ func updateUserTest(s TestSetup, t *testing.T) {
})
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{
ID: user.ID,
Roles: newRoles,

View File

@ -22,7 +22,7 @@ func usersTest(s TestSetup, t *testing.T) {
users, err := resolvers.Users(ctx)
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)
assert.Nil(t, err)
rLen := len(users)

View File

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

View File

@ -1,50 +1,61 @@
package constants
// this constants are configured via env
var (
ADMIN_SECRET = ""
ENV = ""
ENV_PATH = ""
VERSION = ""
DATABASE_TYPE = ""
DATABASE_URL = ""
DATABASE_NAME = ""
SMTP_HOST = ""
SMTP_PORT = ""
SENDER_EMAIL = ""
SENDER_PASSWORD = ""
JWT_TYPE = ""
JWT_SECRET = ""
ALLOWED_ORIGINS = []string{}
AUTHORIZER_URL = ""
APP_URL = ""
PORT = ""
REDIS_URL = ""
IS_PROD = false
COOKIE_NAME = ""
RESET_PASSWORD_URL = ""
DISABLE_EMAIL_VERIFICATION = false
DISABLE_BASIC_AUTHENTICATION = false
DISABLE_MAGIC_LINK_LOGIN = false
DISABLE_LOGIN_PAGE = false
type EnvConst struct {
ADMIN_SECRET string
ENV string
ENV_PATH string
VERSION 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
PORT string
REDIS_URL string
COOKIE_NAME string
ADMIN_COOKIE_NAME string
RESET_PASSWORD_URL string
ENCRYPTION_KEY string `json:"-"`
IS_PROD bool
DISABLE_EMAIL_VERIFICATION bool
DISABLE_BASIC_AUTHENTICATION bool
DISABLE_MAGIC_LINK_LOGIN bool
DISABLE_LOGIN_PAGE bool
// ROLES
ROLES = []string{}
PROTECTED_ROLES = []string{}
DEFAULT_ROLES = []string{}
JWT_ROLE_CLAIM = "role"
ROLES []string
PROTECTED_ROLES []string
DEFAULT_ROLES []string
JWT_ROLE_CLAIM string
// OAuth login
GOOGLE_CLIENT_ID = ""
GOOGLE_CLIENT_SECRET = ""
GITHUB_CLIENT_ID = ""
GITHUB_CLIENT_SECRET = ""
FACEBOOK_CLIENT_ID = ""
FACEBOOK_CLIENT_SECRET = ""
TWITTER_CLIENT_ID = ""
TWITTER_CLIENT_SECRET = ""
GOOGLE_CLIENT_ID string
GOOGLE_CLIENT_SECRET string
GITHUB_CLIENT_ID string
GITHUB_CLIENT_SECRET string
FACEBOOK_CLIENT_ID string
FACEBOOK_CLIENT_SECRET string
// Org envs
ORGANIZATION_NAME = "Authorizer"
ORGANIZATION_LOGO = "https://authorizer.dev/images/logo.png"
)
ORGANIZATION_NAME string
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) {
ctx := context.Background()
conn, err := http.NewConnection(http.ConnectionConfig{
Endpoints: []string{constants.DATABASE_URL},
Endpoints: []string{constants.EnvData.DATABASE_URL},
})
if err != nil {
return nil, err
@ -32,16 +32,16 @@ func initArangodb() (arangoDriver.Database, error) {
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 {
log.Println(constants.DATABASE_NAME + " db exists already")
arangodb, err = arangoClient.Database(nil, constants.DATABASE_NAME)
log.Println(constants.EnvData.DATABASE_NAME + " db exists already")
arangodb, err = arangoClient.Database(nil, constants.EnvData.DATABASE_NAME)
if err != nil {
return nil, err
}
} else {
arangodb, err = arangoClient.CreateDatabase(nil, constants.DATABASE_NAME, nil)
arangodb, err = arangoClient.CreateDatabase(nil, constants.EnvData.DATABASE_NAME, nil)
if err != nil {
return nil, err
}
@ -100,5 +100,15 @@ func initArangodb() (arangoDriver.Database, error) {
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
}

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)
AddSession(session Session) error
DeleteUserSession(userId string) error
AddConfig(config Config) (Config, error)
UpdateConfig(config Config) (Config, error)
GetConfig() (Config, error)
}
type manager struct {
@ -42,6 +45,7 @@ type CollectionList struct {
User string
VerificationRequest string
Session string
Config string
}
var (
@ -54,6 +58,7 @@ var (
User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions",
Config: Prefix + "config",
}
)
@ -61,9 +66,9 @@ func InitDB() {
var sqlDB *gorm.DB
var err error
IsORMSupported = constants.DATABASE_TYPE != enum.Arangodb.String() && constants.DATABASE_TYPE != enum.Mongodb.String()
IsArangoDB = constants.DATABASE_TYPE == enum.Arangodb.String()
IsMongoDB = constants.DATABASE_TYPE == enum.Mongodb.String()
IsORMSupported = constants.EnvData.DATABASE_TYPE != enum.Arangodb.String() && constants.EnvData.DATABASE_TYPE != enum.Mongodb.String()
IsArangoDB = constants.EnvData.DATABASE_TYPE == enum.Arangodb.String()
IsMongoDB = constants.EnvData.DATABASE_TYPE == enum.Mongodb.String()
// sql db orm 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():
sqlDB, err = gorm.Open(postgres.Open(constants.DATABASE_URL), ormConfig)
sqlDB, err = gorm.Open(postgres.Open(constants.EnvData.DATABASE_URL), ormConfig)
break
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
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
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
case enum.Arangodb.String():
arangodb, err := initArangodb()
@ -118,7 +123,7 @@ func InitDB() {
if err != nil {
log.Fatal("Failed to init sqlDB:", err)
} else {
sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{})
sqlDB.AutoMigrate(&User{}, &VerificationRequest{}, &Session{}, &Config{})
}
Mgr = &manager{
sqlDB: sqlDB,

View File

@ -12,7 +12,7 @@ import (
)
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)
mongodbOptions.ConnectTimeout = &maxWait
mongoClient, err := mongo.NewClient(mongodbOptions)
@ -30,7 +30,7 @@ func initMongodb() (*mongo.Database, error) {
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())
userCollection := mongodb.Collection(Collections.User, options.Collection())
@ -73,5 +73,7 @@ func initMongodb() (*mongo.Database, error) {
},
}, options.CreateIndexes())
mongodb.CreateCollection(ctx, Collections.Config, options.CreateCollection())
return mongodb, nil
}

View File

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

View File

@ -27,7 +27,7 @@ type Sender struct {
}
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 {
@ -35,8 +35,8 @@ func (sender Sender) SendMail(Dest []string, Subject, bodyMessage string) error
"To: " + strings.Join(Dest, ",") + "\n" +
"Subject: " + Subject + "\n" + bodyMessage
err := smtp.SendMail(constants.SMTP_HOST+":"+constants.SMTP_PORT,
smtp.PlainAuth("", sender.User, sender.Password, constants.SMTP_HOST),
err := smtp.SendMail(constants.EnvData.SMTP_HOST+":"+constants.EnvData.SMTP_PORT,
smtp.PlainAuth("", sender.User, sender.Password, constants.EnvData.SMTP_HOST),
sender.User, Dest, []byte(msg))
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/utils"
"github.com/google/uuid"
"github.com/joho/godotenv"
)
@ -20,163 +21,169 @@ var (
// InitEnv -> to initialize env and through error if required env are not present
func InitEnv() {
if constants.ENV_PATH == "" {
constants.ENV_PATH = `.env`
if constants.EnvData.ENV_PATH == "" {
constants.EnvData.ENV_PATH = `.env`
}
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 {
log.Printf("error loading %s file", constants.ENV_PATH)
log.Printf("error loading %s file", constants.EnvData.ENV_PATH)
}
if constants.ADMIN_SECRET == "" {
constants.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
if constants.ADMIN_SECRET == "" {
panic("root admin secret is required")
}
if constants.EnvData.ADMIN_SECRET == "" {
constants.EnvData.ADMIN_SECRET = os.Getenv("ADMIN_SECRET")
}
if constants.ENV == "" {
constants.ENV = os.Getenv("ENV")
if constants.ENV == "" {
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 constants.EnvData.DATABASE_TYPE == "" {
constants.EnvData.DATABASE_TYPE = os.Getenv("DATABASE_TYPE")
log.Println(constants.EnvData.DATABASE_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")
}
}
if constants.DATABASE_URL == "" {
constants.DATABASE_URL = os.Getenv("DATABASE_URL")
if constants.EnvData.DATABASE_URL == "" {
constants.EnvData.DATABASE_URL = os.Getenv("DATABASE_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")
}
}
if constants.DATABASE_NAME == "" {
constants.DATABASE_NAME = os.Getenv("DATABASE_NAME")
if constants.DATABASE_NAME == "" {
constants.DATABASE_NAME = "authorizer"
if constants.EnvData.DATABASE_NAME == "" {
constants.EnvData.DATABASE_NAME = os.Getenv("DATABASE_NAME")
if constants.EnvData.DATABASE_NAME == "" {
constants.EnvData.DATABASE_NAME = "authorizer"
}
}
if constants.SMTP_HOST == "" {
constants.SMTP_HOST = os.Getenv("SMTP_HOST")
}
if constants.EnvData.ENV == "" {
constants.EnvData.ENV = os.Getenv("ENV")
if constants.EnvData.ENV == "" {
constants.EnvData.ENV = "production"
}
if constants.SMTP_PORT == "" {
constants.SMTP_PORT = os.Getenv("SMTP_PORT")
}
if constants.SENDER_EMAIL == "" {
constants.SENDER_EMAIL = os.Getenv("SENDER_EMAIL")
}
if constants.SENDER_PASSWORD == "" {
constants.SENDER_PASSWORD = os.Getenv("SENDER_PASSWORD")
}
if constants.JWT_SECRET == "" {
constants.JWT_SECRET = os.Getenv("JWT_SECRET")
}
if constants.JWT_TYPE == "" {
constants.JWT_TYPE = os.Getenv("JWT_TYPE")
}
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = os.Getenv("JWT_ROLE_CLAIM")
if constants.JWT_ROLE_CLAIM == "" {
constants.JWT_ROLE_CLAIM = "role"
if constants.EnvData.ENV == "production" {
constants.EnvData.IS_PROD = true
os.Setenv("GIN_MODE", "release")
} else {
constants.EnvData.IS_PROD = false
}
}
if constants.AUTHORIZER_URL == "" {
constants.AUTHORIZER_URL = strings.TrimSuffix(os.Getenv("AUTHORIZER_URL"), "/")
if constants.EnvData.SMTP_HOST == "" {
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 != "" {
constants.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
constants.EnvData.AUTHORIZER_URL = *ARG_AUTHORIZER_URL
}
}
if constants.PORT == "" {
constants.PORT = os.Getenv("PORT")
if constants.PORT == "" {
constants.PORT = "8080"
if constants.EnvData.PORT == "" {
constants.EnvData.PORT = os.Getenv("PORT")
if constants.EnvData.PORT == "" {
constants.EnvData.PORT = "8080"
}
}
if constants.REDIS_URL == "" {
constants.REDIS_URL = os.Getenv("REDIS_URL")
if constants.EnvData.REDIS_URL == "" {
constants.EnvData.REDIS_URL = os.Getenv("REDIS_URL")
}
if constants.COOKIE_NAME == "" {
constants.COOKIE_NAME = os.Getenv("COOKIE_NAME")
if constants.EnvData.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 == "" {
constants.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
if constants.EnvData.GOOGLE_CLIENT_ID == "" {
constants.EnvData.GOOGLE_CLIENT_ID = os.Getenv("GOOGLE_CLIENT_ID")
}
if constants.GOOGLE_CLIENT_SECRET == "" {
constants.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
if constants.EnvData.GOOGLE_CLIENT_SECRET == "" {
constants.EnvData.GOOGLE_CLIENT_SECRET = os.Getenv("GOOGLE_CLIENT_SECRET")
}
if constants.GITHUB_CLIENT_ID == "" {
constants.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
if constants.EnvData.GITHUB_CLIENT_ID == "" {
constants.EnvData.GITHUB_CLIENT_ID = os.Getenv("GITHUB_CLIENT_ID")
}
if constants.GITHUB_CLIENT_SECRET == "" {
constants.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
if constants.EnvData.GITHUB_CLIENT_SECRET == "" {
constants.EnvData.GITHUB_CLIENT_SECRET = os.Getenv("GITHUB_CLIENT_SECRET")
}
if constants.FACEBOOK_CLIENT_ID == "" {
constants.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
if constants.EnvData.FACEBOOK_CLIENT_ID == "" {
constants.EnvData.FACEBOOK_CLIENT_ID = os.Getenv("FACEBOOK_CLIENT_ID")
}
if constants.FACEBOOK_CLIENT_SECRET == "" {
constants.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
if constants.EnvData.FACEBOOK_CLIENT_SECRET == "" {
constants.EnvData.FACEBOOK_CLIENT_SECRET = os.Getenv("FACEBOOK_CLIENT_SECRET")
}
if constants.RESET_PASSWORD_URL == "" {
constants.RESET_PASSWORD_URL = strings.TrimPrefix(os.Getenv("RESET_PASSWORD_URL"), "/")
if constants.EnvData.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.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
constants.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
constants.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true"
constants.EnvData.DISABLE_BASIC_AUTHENTICATION = os.Getenv("DISABLE_BASIC_AUTHENTICATION") == "true"
constants.EnvData.DISABLE_EMAIL_VERIFICATION = os.Getenv("DISABLE_EMAIL_VERIFICATION") == "true"
constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = os.Getenv("DISABLE_MAGIC_LINK_LOGIN") == "true"
constants.EnvData.DISABLE_LOGIN_PAGE = os.Getenv("DISABLE_LOGIN_PAGE") == "true"
if constants.SMTP_HOST == "" || constants.SENDER_EMAIL == "" || constants.SENDER_PASSWORD == "" {
constants.DISABLE_EMAIL_VERIFICATION = true
constants.DISABLE_MAGIC_LINK_LOGIN = true
if constants.EnvData.SMTP_HOST == "" || constants.EnvData.SENDER_EMAIL == "" || constants.EnvData.SENDER_PASSWORD == "" {
constants.EnvData.DISABLE_EMAIL_VERIFICATION = true
constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true
}
allowedOriginsSplit := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
@ -205,18 +212,10 @@ func InitEnv() {
allowedOrigins = []string{"*"}
}
constants.ALLOWED_ORIGINS = allowedOrigins
constants.EnvData.ALLOWED_ORIGINS = allowedOrigins
if constants.JWT_TYPE == "" {
constants.JWT_TYPE = "HS256"
}
if constants.COOKIE_NAME == "" {
constants.COOKIE_NAME = "authorizer"
}
if constants.DISABLE_EMAIL_VERIFICATION {
constants.DISABLE_MAGIC_LINK_LOGIN = true
if constants.EnvData.DISABLE_EMAIL_VERIFICATION {
constants.EnvData.DISABLE_MAGIC_LINK_LOGIN = true
}
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`)
}
constants.ROLES = roles
constants.DEFAULT_ROLES = defaultRoles
constants.PROTECTED_ROLES = protectedRoles
constants.EnvData.ROLES = roles
constants.EnvData.DEFAULT_ROLES = defaultRoles
constants.EnvData.PROTECTED_ROLES = protectedRoles
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") != "" {
constants.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO")
constants.EnvData.ORGANIZATION_LOGO = os.Getenv("ORGANIZATION_LOGO")
}
}

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

@ -0,0 +1,155 @@
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
}
log.Println("=> persisted data:", jsonData)
// 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
}
}
log.Println("has changed:", hasChanged)
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

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

View File

@ -10,7 +10,7 @@ import (
func DashboardHandler() gin.HandlerFunc {
return func(c *gin.Context) {
isOnboardingCompleted := false
if constants.ADMIN_SECRET != "" && constants.DATABASE_TYPE != "" && constants.DATABASE_URL != "" {
if constants.EnvData.ADMIN_SECRET != "" && constants.EnvData.DATABASE_TYPE != "" && constants.EnvData.DATABASE_URL != "" {
isOnboardingCompleted = true
}

View File

@ -195,7 +195,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// make sure inputRoles don't include protected roles
hasProtectedRole := false
for _, ir := range inputRoles {
if utils.StringSliceContains(constants.PROTECTED_ROLES, ir) {
if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ir) {
hasProtectedRole = true
}
}
@ -238,7 +238,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
// check if it contains protected unassigned role
hasProtectedRole := false
for _, ur := range unasignedRoles {
if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) {
if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) {
hasProtectedRole = true
}
}

View File

@ -34,14 +34,14 @@ func OAuthLoginHandler() gin.HandlerFunc {
// use protected roles verification for admin login only.
// though if not associated with user, it will be rejected from oauth_callback
if !utils.IsValidRoles(append([]string{}, append(constants.ROLES, constants.PROTECTED_ROLES...)...), rolesSplit) {
if !utils.IsValidRoles(append([]string{}, append(constants.EnvData.ROLES, constants.EnvData.PROTECTED_ROLES...)...), rolesSplit) {
c.JSON(400, gin.H{
"error": "invalid role",
})
return
}
} else {
roles = strings.Join(constants.DEFAULT_ROLES, ",")
roles = strings.Join(constants.EnvData.DEFAULT_ROLES, ",")
}
uuid := uuid.New()
@ -57,7 +57,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
}
session.SetSocailLoginState(oauthStateString, enum.Google.String())
// 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)
c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Github.String():
@ -66,7 +66,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break
}
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)
c.Redirect(http.StatusTemporaryRedirect, url)
case enum.Facebook.String():
@ -75,7 +75,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break
}
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)
c.Redirect(http.StatusTemporaryRedirect, url)
default:

View File

@ -22,10 +22,12 @@ func main() {
env.ARG_ENV_FILE = flag.String("env_file", "", "Env file path")
flag.Parse()
constants.VERSION = VERSION
constants.EnvData.VERSION = VERSION
env.InitEnv()
db.InitDB()
env.PersistEnv()
session.InitSession()
oauth.InitOAuth()
utils.InitServer()
@ -35,7 +37,7 @@ func main() {
router.LoadHTMLGlob("templates/*")
// 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 !constants.DISABLE_LOGIN_PAGE {
if !constants.EnvData.DISABLE_LOGIN_PAGE {
app := router.Group("/app")
{
app.Static("/build", "app/build")
@ -50,5 +52,5 @@ func main() {
app.GET("/", handlers.DashboardHandler())
}
router.Run(":" + constants.PORT)
router.Run(":" + constants.EnvData.PORT)
}

View File

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

View File

@ -9,7 +9,7 @@ import (
func CORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
constants.APP_URL = origin
constants.EnvData.APP_URL = origin
if utils.IsValidOrigin(origin) {
c.Writer.Header().Set("Access-Control-Allow-Origin", origin)

View File

@ -28,33 +28,33 @@ var (
func InitOAuth() {
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")
if err != nil {
log.Fatalln("error creating oidc provider for google:", err)
}
OIDCProviders.GoogleOIDC = p
OAuthProviders.GoogleConfig = &oauth2.Config{
ClientID: constants.GOOGLE_CLIENT_ID,
ClientSecret: constants.GOOGLE_CLIENT_SECRET,
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/google",
ClientID: constants.EnvData.GOOGLE_CLIENT_ID,
ClientSecret: constants.EnvData.GOOGLE_CLIENT_SECRET,
RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/google",
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
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{
ClientID: constants.GITHUB_CLIENT_ID,
ClientSecret: constants.GITHUB_CLIENT_SECRET,
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/github",
ClientID: constants.EnvData.GITHUB_CLIENT_ID,
ClientSecret: constants.EnvData.GITHUB_CLIENT_SECRET,
RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/github",
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{
ClientID: constants.FACEBOOK_CLIENT_ID,
ClientSecret: constants.FACEBOOK_CLIENT_SECRET,
RedirectURL: constants.AUTHORIZER_URL + "/oauth_callback/facebook",
ClientID: constants.EnvData.FACEBOOK_CLIENT_ID,
ClientSecret: constants.EnvData.FACEBOOK_CLIENT_SECRET,
RedirectURL: constants.EnvData.AUTHORIZER_URL + "/oauth_callback/facebook",
Endpoint: facebookOAuth2.Endpoint,
Scopes: []string{"public_profile", "email"},
}

View File

@ -20,7 +20,7 @@ func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*mod
log.Println("=> error:", err)
return res, err
}
if params.AdminSecret != constants.ADMIN_SECRET {
if params.AdminSecret != constants.EnvData.ADMIN_SECRET {
return nil, fmt.Errorf(`invalid admin secret`)
}

View File

@ -20,7 +20,7 @@ func ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*mod
if err != nil {
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`)
}
host := gc.Request.Host

View File

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

View File

@ -17,7 +17,7 @@ import (
func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
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`)
}
@ -41,13 +41,13 @@ func MagicLinkLogin(ctx context.Context, params model.MagicLinkLoginInput) (*mod
// define roles for new user
if len(params.Roles) > 0 {
// 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`)
} else {
inputRoles = params.Roles
}
} else {
inputRoles = constants.DEFAULT_ROLES
inputRoles = constants.EnvData.DEFAULT_ROLES
}
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
hasProtectedRole := false
for _, ur := range unasignedRoles {
if utils.StringSliceContains(constants.PROTECTED_ROLES, ur) {
if utils.StringSliceContains(constants.EnvData.PROTECTED_ROLES, ur) {
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
verificationType := enum.MagicLinkLogin.String()
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) {
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`)
}

View File

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

View File

@ -22,7 +22,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
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`)
}
if params.ConfirmPassword != params.Password {
@ -52,13 +52,13 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
if len(params.Roles) > 0 {
// 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`)
} else {
inputRoles = params.Roles
}
} else {
inputRoles = constants.DEFAULT_ROLES
inputRoles = constants.EnvData.DEFAULT_ROLES
}
user := db.User{
@ -103,7 +103,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
}
user.SignupMethods = enum.BasicAuth.String()
if constants.DISABLE_EMAIL_VERIFICATION {
if constants.EnvData.DISABLE_EMAIL_VERIFICATION {
now := time.Now().Unix()
user.EmailVerifiedAt = &now
}
@ -115,7 +115,7 @@ func Signup(ctx context.Context, params model.SignUpInput) (*model.AuthResponse,
roles := strings.Split(user.Roles, ",")
userToReturn := utils.GetResponseUserData(user)
if !constants.DISABLE_EMAIL_VERIFICATION {
if !constants.EnvData.DISABLE_EMAIL_VERIFICATION {
// insert verification request
verificationType := enum.BasicAuthSignup.String()
token, err := utils.CreateVerificationToken(params.Email, verificationType)

View File

@ -112,7 +112,7 @@ func UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User,
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")
}

View File

@ -95,9 +95,9 @@ func RemoveSocialLoginState(key string) {
}
func InitSession() {
if constants.REDIS_URL != "" {
if constants.EnvData.REDIS_URL != "" {
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 {
log.Fatalln("Error parsing redis url:", err)
}

View File

@ -17,7 +17,7 @@ import (
)
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
if tokenType == enum.RefreshToken {
// expires in 1 year
@ -32,11 +32,11 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
json.Unmarshal(userBytes, &userMap)
customClaims := jwt.MapClaims{
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": tokenType.String(),
"allowed_roles": strings.Split(user.Roles, ","),
constants.JWT_ROLE_CLAIM: roles,
"exp": expiresAt,
"iat": time.Now().Unix(),
"token_type": tokenType.String(),
"allowed_roles": strings.Split(user.Roles, ","),
constants.EnvData.JWT_ROLE_CLAIM: roles,
}
for k, v := range userMap {
@ -77,7 +77,7 @@ func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (st
t.Claims = customClaims
token, err := t.SignedString([]byte(constants.JWT_SECRET))
token, err := t.SignedString([]byte(constants.EnvData.JWT_SECRET))
if err != nil {
return "", 0, err
}
@ -89,7 +89,6 @@ func GetAuthToken(gc *gin.Context) (string, error) {
token, err := GetCookie(gc)
if err != nil || token == "" {
// try to check in auth header for cookie
log.Println("cookie not found checking headers")
auth := gc.Request.Header.Get("Authorization")
if auth == "" {
return "", fmt.Errorf(`unauthorized`)
@ -105,7 +104,7 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
claims := jwt.MapClaims{}
_, 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 {
return res, err
@ -126,7 +125,7 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
}
func CreateAdminAuthToken(tokenType enum.TokenType, c *gin.Context) (string, int64, error) {
t := jwt.New(jwt.GetSigningMethod(constants.JWT_TYPE))
t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE))
expiryBound := time.Hour
if tokenType == enum.RefreshToken {
// expires in 1 year
@ -146,9 +145,23 @@ func CreateAdminAuthToken(tokenType enum.TokenType, c *gin.Context) (string, int
t.Claims = customClaims
token, err := t.SignedString([]byte(constants.JWT_SECRET))
token, err := t.SignedString([]byte(constants.EnvData.JWT_SECRET))
if err != nil {
return "", 0, err
}
return token, expiresAt, nil
}
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 ")
}
return token, nil
}

View File

@ -10,21 +10,21 @@ import (
func SetCookie(gc *gin.Context, token string) {
secure := true
httpOnly := true
host, _ := GetHostParts(constants.AUTHORIZER_URL)
domain := GetDomainName(constants.AUTHORIZER_URL)
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
domain := GetDomainName(constants.EnvData.AUTHORIZER_URL)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(constants.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
gc.SetCookie(constants.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly)
gc.SetCookie(constants.EnvData.COOKIE_NAME, token, 3600, "/", host, secure, httpOnly)
gc.SetCookie(constants.EnvData.COOKIE_NAME+"-client", token, 3600, "/", domain, secure, httpOnly)
}
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 {
cookie, err = gc.Request.Cookie(constants.COOKIE_NAME + "-client")
cookie, err = gc.Request.Cookie(constants.EnvData.COOKIE_NAME + "-client")
if err != nil {
return "", err
}
@ -37,29 +37,37 @@ func DeleteCookie(gc *gin.Context) {
secure := true
httpOnly := true
host, _ := GetHostParts(constants.AUTHORIZER_URL)
domain := GetDomainName(constants.AUTHORIZER_URL)
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
domain := GetDomainName(constants.EnvData.AUTHORIZER_URL)
if domain != "localhost" {
domain = "." + domain
}
gc.SetSameSite(http.SameSiteNoneMode)
gc.SetCookie(constants.COOKIE_NAME, "", -1, "/", host, secure, httpOnly)
gc.SetCookie(constants.COOKIE_NAME+"-client", "", -1, "/", domain, secure, httpOnly)
gc.SetCookie(constants.EnvData.COOKIE_NAME, "", -1, "/", host, 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.AUTHORIZER_URL)
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
gc.SetCookie("authorizer-admin", token, 3600, "/", host, secure, httpOnly)
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.AUTHORIZER_URL)
host, _ := GetHostParts(constants.EnvData.AUTHORIZER_URL)
gc.SetCookie("authorizer-admin", "", -1, "/", host, secure, httpOnly)
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>
</body>
</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)
return sender.SendMail(Receiver, Subject, bodyMessage)
@ -108,8 +108,8 @@ func SendVerificationMail(toEmail, token string) error {
// SendForgotPasswordMail to send verification email
func SendForgotPasswordMail(toEmail, token, host string) error {
if constants.RESET_PASSWORD_URL == "" {
constants.RESET_PASSWORD_URL = constants.AUTHORIZER_URL + "/app/reset-password"
if constants.EnvData.RESET_PASSWORD_URL == "" {
constants.EnvData.RESET_PASSWORD_URL = constants.EnvData.AUTHORIZER_URL + "/app/reset-password"
}
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>
</body>
</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)

View File

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

View File

@ -16,7 +16,7 @@ func IsValidEmail(email 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
}
@ -24,7 +24,7 @@ func IsValidOrigin(url string) bool {
hostName, port := GetHostParts(url)
currentOrigin := hostName + ":" + port
for _, origin := range constants.ALLOWED_ORIGINS {
for _, origin := range constants.EnvData.ALLOWED_ORIGINS {
replacedString := origin
// if has regex whitelisted domains
if strings.Contains(origin, "*") {
@ -50,12 +50,17 @@ func IsValidOrigin(url string) bool {
}
func IsSuperAdmin(gc *gin.Context) bool {
secret := gc.Request.Header.Get("x-authorizer-admin-secret")
if secret == "" {
return false
token, err := GetAdminAuthToken(gc)
if err != nil {
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 {

View File

@ -20,23 +20,23 @@ type CustomClaim struct {
}
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{
&jwt.StandardClaims{
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
},
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) {
claims := &CustomClaim{}
_, 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 {
return claims, err