feat: add _update_config mutation

This commit is contained in:
Lakhan Samani 2021-12-31 17:03:37 +05:30
parent 217410e9a4
commit 9c8e9baa39
14 changed files with 2316 additions and 55 deletions

View File

@ -9,7 +9,7 @@ import (
"github.com/stretchr/testify/assert"
)
func aminLoginTests(s TestSetup, t *testing.T) {
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{

View File

@ -10,7 +10,7 @@ import (
"github.com/stretchr/testify/assert"
)
func aminSessionTests(s TestSetup, t *testing.T) {
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)

View File

@ -2,6 +2,7 @@ package test
import (
"context"
"log"
"testing"
"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) {
ctx := context.Background()
meta, err := resolvers.Meta(ctx)
log.Println("=> meta:", meta)
assert.Nil(t, err)
assert.False(t, meta.IsFacebookLoginEnabled)
assert.False(t, meta.IsGoogleLoginEnabled)

View File

@ -6,6 +6,7 @@ import (
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/env"
)
func TestResolvers(t *testing.T) {
@ -19,6 +20,7 @@ func TestResolvers(t *testing.T) {
constants.EnvData.DATABASE_URL = dbURL
constants.EnvData.DATABASE_TYPE = dbType
db.InitDB()
env.PersistEnv()
s := testSetup()
defer s.Server.Close()
@ -42,8 +44,9 @@ func TestResolvers(t *testing.T) {
usersTest(s, t)
deleteUserTest(s, t)
updateUserTest(s, t)
aminLoginTests(s, t)
aminSessionTests(s, t)
adminLoginTests(s, t)
adminSessionTests(s, t)
updateConfigTests(s, t)
})
}
}

View File

@ -71,6 +71,7 @@ func testSetup() TestSetup {
}
constants.EnvData.ENV_PATH = "../../.env.sample"
env.InitEnv()
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

@ -59,8 +59,6 @@ func PersistEnv() error {
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
@ -120,7 +118,6 @@ func PersistEnv() error {
}
}
log.Println("has changed:", hasChanged)
if hasChanged {
jsonBytes, err := json.Marshal(jsonData)
if err != nil {
@ -132,23 +129,23 @@ func PersistEnv() error {
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
}
}
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

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,42 @@ type AuthResponse struct {
User *User `json:"user"`
}
type Config struct {
AdminSecret *string `json:"ADMIN_SECRET"`
Version *string `json:"VERSION"`
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 {
Email string `json:"email"`
}
@ -82,6 +118,42 @@ type SignUpInput struct {
Roles []string `json:"roles"`
}
type UpdateConfigInput struct {
AdminSecret *string `json:"ADMIN_SECRET"`
Version *string `json:"VERSION"`
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 {
OldPassword *string `json:"old_password"`
NewPassword *string `json:"new_password"`

View File

@ -67,6 +67,78 @@ type AdminLoginResponse {
access_token: String!
}
type Config {
ADMIN_SECRET: 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
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
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
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!
}
@ -162,15 +234,17 @@ type Mutation {
# admin only apis
_delete_user(params: DeleteUserInput!): Response!
_update_user(params: UpdateUserInput!): User!
_admin_login(params: AdminLoginInput!): AdminLoginResponse
_admin_login(params: AdminLoginInput!): AdminLoginResponse!
_update_config(params: UpdateConfigInput!): Response!
}
type Query {
meta: Meta!
session(roles: [String!]): AuthResponse
session(roles: [String!]): AuthResponse!
profile: User!
# admin only apis
_users: [User!]!
_verification_requests: [VerificationRequest!]!
_admin_session: AdminLoginResponse
_admin_session: AdminLoginResponse!
_config: Config!
}

View File

@ -5,6 +5,7 @@ package graph
import (
"context"
"fmt"
"github.com/authorizerdev/authorizer/server/graph/generated"
"github.com/authorizerdev/authorizer/server/graph/model"
@ -59,6 +60,10 @@ func (r *mutationResolver) AdminLogin(ctx context.Context, params model.AdminLog
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) {
return resolvers.Meta(ctx)
}
@ -83,13 +88,15 @@ func (r *queryResolver) AdminSession(ctx context.Context) (*model.AdminLoginResp
return resolvers.AdminSession(ctx)
}
func (r *queryResolver) Config(ctx context.Context) (*model.Config, error) {
panic(fmt.Errorf("not implemented"))
}
// Mutation returns generated.MutationResolver implementation.
func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResolver{r} }
// Query returns generated.QueryResolver implementation.
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
type (
mutationResolver struct{ *Resolver }
queryResolver struct{ *Resolver }
)
type mutationResolver struct{ *Resolver }
type queryResolver struct{ *Resolver }

View File

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

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

@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"log"
"net/url"
"os"
"strings"
"time"
@ -140,10 +141,20 @@ func GetAdminAuthToken(gc *gin.Context) (string, error) {
token = strings.TrimPrefix(auth, "Bearer ")
err = bcrypt.CompareHashAndPassword([]byte(token), []byte(constants.EnvData.ADMIN_SECRET))
if err != nil {
return "", fmt.Errorf(`unauthorized`)
}
}
// 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
}