Merge branch 'main' of https://github.com/authorizerdev/authorizer into fix/dashboard

This commit is contained in:
Anik Ghosh 2022-01-31 13:29:38 +05:30
commit 115607cb6b
60 changed files with 149 additions and 147 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
app/favicon_io/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 528 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -64,6 +64,7 @@ export const UserDetailsQuery = `
birthdate
phone_number
picture
signup_methods
roles
created_at
}

View File

@ -63,6 +63,7 @@ interface userDataTypes {
birthdate: string;
phone_number: string;
picture: string;
signup_methods: string;
roles: [string];
created_at: number;
}

View File

@ -51,7 +51,10 @@ export const getObjectDiff = (obj1: any, obj2: any) => {
} else if (
_.isEqual(obj1[key], obj2[key]) ||
(obj1[key] === null && obj2[key] === '') ||
(obj1[key] === [] && obj2[key] === null)
(obj1[key] &&
Array.isArray(obj1[key]) &&
obj1[key].length === 0 &&
obj2[key] === null)
) {
const resultKeyIndex = result.indexOf(key);
result.splice(resultKeyIndex, 1);

View File

@ -16,6 +16,7 @@ const (
// EnvKeyVersion key for build arg version
EnvKeyVersion = "VERSION"
// EnvKeyAuthorizerURL key for env variable AUTHORIZER_URL
// TODO: remove support AUTHORIZER_URL env
EnvKeyAuthorizerURL = "AUTHORIZER_URL"
// EnvKeyPort key for env variable PORT
EnvKeyPort = "PORT"

View File

@ -13,7 +13,8 @@ import (
func SetAdminCookie(gc *gin.Context, token string) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), token, 3600, "/", host, secure, httpOnly)
}
@ -38,7 +39,8 @@ func GetAdminCookie(gc *gin.Context) (string, error) {
func DeleteAdminCookie(gc *gin.Context) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
gc.SetCookie(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminCookieName), "", -1, "/", host, secure, httpOnly)
}

View File

@ -19,8 +19,9 @@ import (
func SetCookie(gc *gin.Context, accessToken, refreshToken, fingerprintHash string) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
domain := utils.GetDomainName(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}
@ -86,9 +87,9 @@ func GetFingerPrintCookie(gc *gin.Context) (string, error) {
func DeleteCookie(gc *gin.Context) {
secure := true
httpOnly := true
host, _ := utils.GetHostParts(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
domain := utils.GetDomainName(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL))
hostname := utils.GetHost(gc)
host, _ := utils.GetHostParts(hostname)
domain := utils.GetDomainName(hostname)
if domain != "localhost" {
domain = "." + domain
}

View File

@ -6,6 +6,6 @@ type Env struct {
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id"`
EnvData string `gorm:"type:text" json:"env" bson:"env"`
Hash string `gorm:"type:text" json:"hash" bson:"hash"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at"`
}

View File

@ -8,6 +8,6 @@ type Session struct {
User User `json:"-" bson:"-"`
UserAgent string `json:"user_agent" bson:"user_agent"`
IP string `json:"ip" bson:"ip"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
}

View File

@ -25,8 +25,8 @@ type User struct {
PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
Roles string `json:"roles" bson:"roles"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at"`
}
func (user *User) AsAPIUser() *model.User {

View File

@ -9,8 +9,8 @@ type VerificationRequest struct {
Token string `gorm:"type:text" json:"token" bson:"token"`
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"`
CreatedAt int64 `json:"created_at" bson:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"`
}

View File

@ -15,8 +15,10 @@ func (p *provider) AddEnv(env models.Env) (models.Env, error) {
}
env.Key = env.ID
result := p.db.Create(&env)
env.CreatedAt = time.Now().Unix()
env.UpdatedAt = time.Now().Unix()
result := p.db.Create(&env)
if result.Error != nil {
log.Println("error adding config:", result.Error)
return env, result.Error

View File

@ -2,6 +2,7 @@ package sql
import (
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid"
@ -15,6 +16,8 @@ func (p *provider) AddSession(session models.Session) error {
}
session.Key = session.ID
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
res := p.db.Clauses(
clause.OnConflict{
DoNothing: true,

View File

@ -23,6 +23,8 @@ func (p *provider) AddUser(user models.User) (models.User, error) {
user.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
}
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
user.Key = user.ID
result := p.db.Clauses(
clause.OnConflict{

View File

@ -2,6 +2,7 @@ package sql
import (
"log"
"time"
"github.com/authorizerdev/authorizer/server/db/models"
"github.com/authorizerdev/authorizer/server/graph/model"
@ -16,6 +17,8 @@ func (p *provider) AddVerificationRequest(verificationRequest models.Verificatio
}
verificationRequest.Key = verificationRequest.ID
verificationRequest.CreatedAt = time.Now().Unix()
verificationRequest.UpdatedAt = time.Now().Unix()
result := p.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "email"}, {Name: "identifier"}},
DoUpdates: clause.AssignmentColumns([]string{"token", "expires_at"}),

View File

@ -6,10 +6,10 @@ import (
)
// SendForgotPasswordMail to send forgot password email
func SendForgotPasswordMail(toEmail, token, host string) error {
func SendForgotPasswordMail(toEmail, token, hostname string) error {
resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
if resetPasswordUrl == "" {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)+"/app/reset-password")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyResetPasswordURL, hostname+"/app/reset-password")
}
// The receiver needs to be in slice as the receive supports multiple receiver

View File

@ -6,7 +6,7 @@ import (
)
// SendVerificationMail to send verification email
func SendVerificationMail(toEmail, token string) error {
func SendVerificationMail(toEmail, token, hostname string) error {
// The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail}
@ -99,7 +99,7 @@ func SendVerificationMail(toEmail, token string) error {
data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName)
data["verification_url"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/verify_email?token=" + token
data["verification_url"] = hostname + "/verify_email?token=" + token
message = addEmailTemplate(message, data, "verify_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)

9
server/env/env.go vendored
View File

@ -31,8 +31,6 @@ func InitEnv() {
}
}
// set authorizer url to empty string so that fresh url is obtained with every server start
envData.StringEnv[constants.EnvKeyAuthorizerURL] = ""
if envData.StringEnv[constants.EnvKeyAppURL] == "" {
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
}
@ -246,10 +244,9 @@ func InitEnv() {
trimVal := strings.TrimSpace(val)
if trimVal != "" {
roles = append(roles, trimVal)
}
if utils.StringSliceContains(defaultRoleSplit, trimVal) {
defaultRoles = append(defaultRoles, trimVal)
if utils.StringSliceContains(defaultRoleSplit, trimVal) {
defaultRoles = append(defaultRoles, trimVal)
}
}
}

View File

@ -6,7 +6,6 @@ require (
github.com/99designs/gqlgen v0.14.0
github.com/arangodb/go-driver v1.2.1
github.com/coreos/go-oidc/v3 v3.1.0
github.com/gin-contrib/location v0.0.2
github.com/gin-gonic/gin v1.7.2
github.com/go-playground/validator/v10 v10.8.0 // indirect
github.com/go-redis/redis/v8 v8.11.0

View File

@ -82,11 +82,8 @@ github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/gin-contrib/location v0.0.2 h1:QZKh1+K/LLR4KG/61eIO3b7MLuKi8tytQhV6texLgP4=
github.com/gin-contrib/location v0.0.2/go.mod h1:NGoidiRlf0BlA/VKSVp+g3cuSMeTmip/63PhEjRhUAc=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.2 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -100,7 +97,6 @@ github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8c
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.8.0 h1:1kAa0fCrnpv+QYdkdcRzrRM7AyYs5o8+jZdJCz9xj6k=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk=

View File

@ -54,7 +54,6 @@ type ComplexityRoot struct {
AdminSecret func(childComplexity int) int
AllowedOrigins func(childComplexity int) int
AppURL func(childComplexity int) int
AuthorizerURL func(childComplexity int) int
CookieName func(childComplexity int) int
CustomAccessTokenScript func(childComplexity int) int
DatabaseName func(childComplexity int) int
@ -280,13 +279,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Env.AppURL(childComplexity), true
case "Env.AUTHORIZER_URL":
if e.complexity.Env.AuthorizerURL == nil {
break
}
return e.complexity.Env.AuthorizerURL(childComplexity), true
case "Env.COOKIE_NAME":
if e.complexity.Env.CookieName == nil {
break
@ -1215,7 +1207,6 @@ type Env {
JWT_TYPE: String
JWT_SECRET: String
ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String
REDIS_URL: String
COOKIE_NAME: String
@ -1250,7 +1241,6 @@ input UpdateEnvInput {
JWT_TYPE: String
JWT_SECRET: String
ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String
REDIS_URL: String
COOKIE_NAME: String
@ -2271,38 +2261,6 @@ func (ec *executionContext) _Env_ALLOWED_ORIGINS(ctx context.Context, field grap
return ec.marshalOString2ᚕstringᚄ(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_AUTHORIZER_URL(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "Env",
Field: field,
Args: nil,
IsMethod: false,
IsResolver: false,
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.AuthorizerURL, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
return graphql.Null
}
res := resTmp.(*string)
fc.Result = res
return ec.marshalOString2ᚖstring(ctx, field.Selections, res)
}
func (ec *executionContext) _Env_APP_URL(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
@ -7094,14 +7052,6 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
if err != nil {
return it, err
}
case "AUTHORIZER_URL":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("AUTHORIZER_URL"))
it.AuthorizerURL, err = ec.unmarshalOString2ᚖstring(ctx, v)
if err != nil {
return it, err
}
case "APP_URL":
var err error
@ -7591,8 +7541,6 @@ func (ec *executionContext) _Env(ctx context.Context, sel ast.SelectionSet, obj
out.Values[i] = ec._Env_JWT_SECRET(ctx, field, obj)
case "ALLOWED_ORIGINS":
out.Values[i] = ec._Env_ALLOWED_ORIGINS(ctx, field, obj)
case "AUTHORIZER_URL":
out.Values[i] = ec._Env_AUTHORIZER_URL(ctx, field, obj)
case "APP_URL":
out.Values[i] = ec._Env_APP_URL(ctx, field, obj)
case "REDIS_URL":

View File

@ -35,7 +35,6 @@ type Env struct {
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"`
@ -155,7 +154,6 @@ type UpdateEnvInput struct {
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"`

View File

@ -98,7 +98,6 @@ type Env {
JWT_TYPE: String
JWT_SECRET: String
ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String
REDIS_URL: String
COOKIE_NAME: String
@ -133,7 +132,6 @@ input UpdateEnvInput {
JWT_TYPE: String
JWT_SECRET: String
ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String
REDIS_URL: String
COOKIE_NAME: String

View File

@ -22,6 +22,7 @@ type State struct {
// AppHandler is the handler for the /app route
func AppHandler() gin.HandlerFunc {
return func(c *gin.Context) {
hostname := utils.GetHost(c)
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableLoginPage) {
c.JSON(400, gin.H{"error": "login page is not enabled"})
return
@ -32,7 +33,8 @@ func AppHandler() gin.HandlerFunc {
var stateObj State
if state == "" {
stateObj.AuthorizerURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL)
stateObj.AuthorizerURL = hostname
stateObj.RedirectURL = stateObj.AuthorizerURL + "/app"
} else {
@ -62,7 +64,7 @@ func AppHandler() gin.HandlerFunc {
}
// validate host and domain of authorizer url
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) {
if strings.TrimSuffix(stateObj.AuthorizerURL, "/") != hostname {
c.JSON(400, gin.H{"error": "invalid host url"})
return
}

View File

@ -99,6 +99,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.SignupMethods = signupMethod
user.Password = existingUser.Password
if user.EmailVerifiedAt == nil {
now := time.Now().Unix()
user.EmailVerifiedAt = &now
}
// There multiple scenarios with roles here in social login
// 1. user has access to protected roles + roles and trying to login
// 2. user has not signed up for one of the available role but trying to signup.

View File

@ -16,7 +16,7 @@ import (
// OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
func OAuthLoginHandler() gin.HandlerFunc {
return func(c *gin.Context) {
// TODO validate redirect URL
hostname := utils.GetHost(c)
redirectURL := c.Query("redirectURL")
roles := c.Query("roles")
@ -56,7 +56,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
}
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle)
// during the init of OAuthProvider authorizer url might be empty
oauth.OAuthProviders.GoogleConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/google"
oauth.OAuthProviders.GoogleConfig.RedirectURL = hostname + "/oauth_callback/google"
url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodGithub:
@ -65,7 +65,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break
}
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub)
oauth.OAuthProviders.GithubConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/github"
oauth.OAuthProviders.GithubConfig.RedirectURL = hostname + "/oauth_callback/github"
url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodFacebook:
@ -74,7 +74,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break
}
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook)
oauth.OAuthProviders.FacebookConfig.RedirectURL = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/facebook"
oauth.OAuthProviders.FacebookConfig.RedirectURL = hostname + "/oauth_callback/facebook"
url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url)
default:

View File

@ -2,22 +2,13 @@ package middlewares
import (
"context"
"log"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
// GinContextToContextMiddleware is a middleware to add gin context in context
func GinContextToContextMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
if envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) == "" {
url := location.Get(c)
log.Println("=> setting authorizer url to: " + url.Scheme + "://" + c.Request.Host)
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAuthorizerURL, url.Scheme+"://"+c.Request.Host)
}
ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
c.Request = c.Request.WithContext(ctx)
c.Next()

View File

@ -43,7 +43,7 @@ func InitOAuth() {
OAuthProviders.GoogleConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGoogleClientSecret),
RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/google",
RedirectURL: "/oauth_callback/google",
Endpoint: OIDCProviders.GoogleOIDC.Endpoint(),
Scopes: []string{oidc.ScopeOpenID, "profile", "email"},
}
@ -52,7 +52,7 @@ func InitOAuth() {
OAuthProviders.GithubConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyGithubClientSecret),
RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/github",
RedirectURL: "/oauth_callback/github",
Endpoint: githubOAuth2.Endpoint,
}
}
@ -60,7 +60,7 @@ func InitOAuth() {
OAuthProviders.FacebookConfig = &oauth2.Config{
ClientID: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientID),
ClientSecret: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyFacebookClientSecret),
RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL) + "/oauth_callback/facebook",
RedirectURL: "/oauth_callback/facebook",
Endpoint: facebookOAuth2.Endpoint,
Scopes: []string{"public_profile", "email"},
}

View File

@ -41,7 +41,6 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
jwtSecret := store.StringEnv[constants.EnvKeyJwtSecret]
jwtRoleClaim := store.StringEnv[constants.EnvKeyJwtRoleClaim]
allowedOrigins := store.SliceEnv[constants.EnvKeyAllowedOrigins]
authorizerURL := store.StringEnv[constants.EnvKeyAuthorizerURL]
appURL := store.StringEnv[constants.EnvKeyAppURL]
redisURL := store.StringEnv[constants.EnvKeyRedisURL]
cookieName := store.StringEnv[constants.EnvKeyCookieName]
@ -77,7 +76,6 @@ func EnvResolver(ctx context.Context) (*model.Env, error) {
JwtSecret: &jwtSecret,
JwtRoleClaim: &jwtRoleClaim,
AllowedOrigins: allowedOrigins,
AuthorizerURL: &authorizerURL,
AppURL: &appURL,
RedisURL: &redisURL,
CookieName: &cookieName,

View File

@ -27,7 +27,6 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableBasicAuthentication) {
return res, fmt.Errorf(`basic authentication is disabled for this instance`)
}
host := gc.Request.Host
params.Email = strings.ToLower(params.Email)
if !utils.IsValidEmail(params.Email) {
@ -39,7 +38,8 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
return res, fmt.Errorf(`user with this email not found`)
}
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword)
hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, constants.VerificationTypeForgotPassword, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@ -52,7 +52,7 @@ func ForgotPasswordResolver(ctx context.Context, params model.ForgotPasswordInpu
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendForgotPasswordMail(params.Email, verificationToken, host)
email.SendForgotPasswordMail(params.Email, verificationToken, hostname)
}()
res = &model.Response{

View File

@ -20,6 +20,10 @@ import (
// MagicLinkLoginResolver is a resolver for magic link login mutation
func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInput) (*model.Response, error) {
var res *model.Response
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
return res, err
}
if envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableMagicLinkLogin) {
return res, fmt.Errorf(`magic link login is disabled for this instance`)
@ -102,10 +106,11 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
}
}
hostname := utils.GetHost(gc)
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request
verificationType := constants.VerificationTypeMagicLinkLogin
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType)
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@ -118,7 +123,7 @@ func MagicLinkLoginResolver(ctx context.Context, params model.MagicLinkLoginInpu
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(params.Email, verificationToken)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
}

View File

@ -18,6 +18,10 @@ import (
// ResendVerifyEmailResolver is a resolver for resend verify email mutation
func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error) {
var res *model.Response
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
return res, err
}
params.Email = strings.ToLower(params.Email)
if !utils.IsValidEmail(params.Email) {
@ -39,7 +43,8 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
log.Println("error deleting verification request:", err)
}
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier)
hostname := utils.GetHost(gc)
verificationToken, err := token.CreateVerificationToken(params.Email, params.Identifier, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@ -52,7 +57,7 @@ func ResendVerifyEmailResolver(ctx context.Context, params model.ResendVerifyEma
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(params.Email, verificationToken)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
res = &model.Response{

View File

@ -119,10 +119,11 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
roles := strings.Split(user.Roles, ",")
userToReturn := user.AsAPIUser()
hostname := utils.GetHost(gc)
if !envstore.EnvInMemoryStoreObj.GetBoolStoreEnvVariable(constants.EnvKeyDisableEmailVerification) {
// insert verification request
verificationType := constants.VerificationTypeBasicAuthSignup
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType)
verificationToken, err := token.CreateVerificationToken(params.Email, verificationType, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@ -135,7 +136,7 @@ func SignupResolver(ctx context.Context, params model.SignUpInput) (*model.AuthR
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(params.Email, verificationToken)
email.SendVerificationMail(params.Email, verificationToken, hostname)
}()
res = &model.AuthResponse{

View File

@ -13,6 +13,8 @@ import (
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/oauth"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
)
@ -115,6 +117,8 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
// Update local store
envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData)
sessionstore.InitSession()
oauth.InitOAuth()
// Fetch the current db store and update it
env, err := db.Provider.GetEnv()

View File

@ -116,12 +116,13 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
cookie.DeleteCookie(gc)
hostname := utils.GetHost(gc)
user.Email = newEmail
user.EmailVerifiedAt = nil
hasEmailChanged = true
// insert verification request
verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType)
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@ -134,7 +135,7 @@ func UpdateProfileResolver(ctx context.Context, params model.UpdateProfileInput)
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(newEmail, verificationToken)
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
}

View File

@ -98,11 +98,12 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
cookie.DeleteCookie(gc)
hostname := utils.GetHost(gc)
user.Email = newEmail
user.EmailVerifiedAt = nil
// insert verification request
verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType)
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
if err != nil {
log.Println(`error generating token`, err)
}
@ -115,7 +116,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
// exec it as go routin so that we can reduce the api latency
go func() {
email.SendVerificationMail(newEmail, verificationToken)
email.SendVerificationMail(newEmail, verificationToken, hostname)
}()
}
@ -127,7 +128,7 @@ func UpdateUserResolver(ctx context.Context, params model.UpdateUserInput) (*mod
inputRoles = append(inputRoles, *item)
}
if !utils.IsValidRoles(append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...), inputRoles) {
if !utils.IsValidRoles(inputRoles, append([]string{}, append(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyRoles), envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyProtectedRoles)...)...)) {
return res, fmt.Errorf("invalid list of roles")
}

View File

@ -3,14 +3,13 @@ package routes
import (
"github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
// InitRouter initializes gin router
func InitRouter() *gin.Engine {
router := gin.Default()
router.Use(location.Default())
// router.Use(location.Default())
router.Use(middlewares.GinContextToContextMiddleware())
router.Use(middlewares.CORSMiddleware())
@ -25,14 +24,16 @@ func InitRouter() *gin.Engine {
// login page app related routes.
app := router.Group("/app")
{
app.Static("/favicon_io", "app/favicon_io")
app.Static("/build", "app/build")
app.GET("/", handlers.AppHandler())
app.GET("/reset-password", handlers.AppHandler())
app.GET("/:page", handlers.AppHandler())
}
// dashboard related routes
dashboard := router.Group("/dashboard")
{
dashboard.Static("/favicon_io", "dashboard/favicon_io")
dashboard.Static("/build", "dashboard/build")
dashboard.GET("/", handlers.DashboardHandler())
dashboard.GET("/:page", handlers.DashboardHandler())

View File

@ -7,13 +7,12 @@ import (
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/google/uuid"
"github.com/stretchr/testify/assert"
)
func adminSignupTests(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should complete admin login`, func(t *testing.T) {
t.Run(`should complete admin signup`, func(t *testing.T) {
_, ctx := createContext(s)
_, err := resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
AdminSecret: "admin",
@ -24,7 +23,7 @@ func adminSignupTests(t *testing.T, s TestSetup) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "")
_, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
AdminSecret: uuid.New().String(),
AdminSecret: "admin123",
})
assert.Nil(t, err)

View File

@ -14,16 +14,13 @@ func TestEnvs(t *testing.T) {
env.InitEnv()
store := envstore.EnvInMemoryStoreObj.GetEnvStoreClone()
assert.Equal(t, store.StringEnv[constants.EnvKeyAdminSecret], "admin")
assert.Equal(t, store.StringEnv[constants.EnvKeyEnv], "production")
assert.False(t, store.BoolEnv[constants.EnvKeyDisableEmailVerification])
assert.False(t, store.BoolEnv[constants.EnvKeyDisableMagicLinkLogin])
assert.False(t, store.BoolEnv[constants.EnvKeyDisableBasicAuthentication])
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtType], "HS256")
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtSecret], "random_string")
assert.Equal(t, store.StringEnv[constants.EnvKeyJwtRoleClaim], "role")
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyRoles], []string{"user"})
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyDefaultRoles], []string{"user"})
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyProtectedRoles], []string{"admin"})
assert.EqualValues(t, store.SliceEnv[constants.EnvKeyAllowedOrigins], []string{"*"})
}

View File

@ -1,6 +1,7 @@
package test
import (
"log"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
@ -20,20 +21,20 @@ func TestResolvers(t *testing.T) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseURL, dbURL)
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyDatabaseType, dbType)
env.InitEnv()
s := testSetup()
defer s.Server.Close()
db.InitDB()
// clean the persisted config for test to use fresh config
envData, err := db.Provider.GetEnv()
log.Println("=> envData:", envstore.EnvInMemoryStoreObj.GetEnvStoreClone())
if err == nil {
envData.EnvData = ""
db.Provider.UpdateEnv(envData)
}
env.PersistEnv()
s := testSetup()
defer s.Server.Close()
t.Run("should pass tests for "+dbType, func(t *testing.T) {
// admin tests
adminSignupTests(t, s)

View File

@ -14,7 +14,6 @@ import (
"github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares"
"github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin"
)
@ -73,13 +72,17 @@ func testSetup() TestSetup {
}
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyEnvPath, "../../.env.sample")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpHost, "smtp.yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPort, "2525")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpUsername, "lakhan@yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySmtpPassword, "test")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeySenderEmail, "info@yopmail.com")
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.SliceStoreIdentifier, constants.EnvKeyProtectedRoles, []string{"admin"})
env.InitEnv()
sessionstore.InitSession()
w := httptest.NewRecorder()
c, r := gin.CreateTestContext(w)
r.Use(location.Default())
r.Use(middlewares.GinContextToContextMiddleware())
r.Use(middlewares.CORSMiddleware())

View File

@ -24,7 +24,7 @@ func updateUserTest(t *testing.T, s TestSetup) {
})
user := *signupRes.User
adminRole := "admin"
adminRole := "supplier"
userRole := "user"
newRoles := []*string{&adminRole, &userRole}
_, err := resolvers.UpdateUserResolver(ctx, model.UpdateUserInput{

View File

@ -8,9 +8,9 @@ import (
)
func TestGetHostName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com:80"
url := "http://test.herokuapp.com:80"
host, port := utils.GetHostParts(authorizer_url)
host, port := utils.GetHostParts(url)
expectedHost := "test.herokuapp.com"
assert.Equal(t, host, expectedHost, "hostname should be equal")
@ -18,9 +18,9 @@ func TestGetHostName(t *testing.T) {
}
func TestGetDomainName(t *testing.T) {
authorizer_url := "http://test.herokuapp.com"
url := "http://test.herokuapp.com"
got := utils.GetDomainName(authorizer_url)
got := utils.GetDomainName(url)
want := "herokuapp.com"
assert.Equal(t, got, want, "domain name should be equal")

View File

@ -2,7 +2,6 @@ package token
import (
"fmt"
"log"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie"
@ -25,7 +24,7 @@ func GetAdminAuthToken(gc *gin.Context) (string, error) {
}
err = bcrypt.CompareHashAndPassword([]byte(token), []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)))
log.Println("error comparing hash:", err)
if err != nil {
return "", fmt.Errorf(`unauthorized`)
}

View File

@ -23,7 +23,7 @@ type CustomClaim struct {
}
// CreateVerificationToken creates a verification JWT token
func CreateVerificationToken(email string, tokenType string) (string, error) {
func CreateVerificationToken(email, tokenType, hostname string) (string, error) {
t := jwt.New(jwt.GetSigningMethod(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)))
t.Claims = &CustomClaim{
@ -31,7 +31,7 @@ func CreateVerificationToken(email string, tokenType string) (string, error) {
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
},
tokenType,
VerificationRequestToken{Email: email, Host: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAuthorizerURL), RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)},
VerificationRequestToken{Email: email, Host: hostname, RedirectURL: envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAppURL)},
}
return t.SignedString([]byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtSecret)))

View File

@ -34,3 +34,16 @@ func SaveSessionInDB(userId string, c *gin.Context) {
log.Println("=> session saved in db:", sessionData)
}
}
// RemoveDuplicateString removes duplicate strings from a string slice
func RemoveDuplicateString(strSlice []string) []string {
allKeys := make(map[string]bool)
list := []string{}
for _, item := range strSlice {
if _, value := allKeys[item]; !value {
allKeys[item] = true
list = append(list, item)
}
}
return list
}

View File

@ -1,10 +1,23 @@
package utils
import (
"log"
"net/url"
"strings"
"github.com/gin-gonic/gin"
)
// GetHost returns hostname from request context
func GetHost(c *gin.Context) string {
scheme := "http"
if c.Request.TLS != nil {
scheme = "https"
}
log.Println("=> url:", scheme+"://"+c.Request.Host)
return scheme + "://" + c.Request.Host
}
// GetHostName function returns hostname and port
func GetHostParts(uri string) (string, string) {
tempURI := uri

View File

@ -2,8 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>{{.data.organizationName}}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="apple-touch-icon" sizes="180x180" href="/app/favicon_io/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/app/favicon_io/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/app/favicon_io/favicon-16x16.png">
<title>Document</title>
<link rel="stylesheet" href="/app/build/index.css">
<script>

View File

@ -2,8 +2,12 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Authorizer | Dashboard</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="apple-touch-icon" sizes="180x180" href="/dashboard/favicon_io/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="/dashboard/favicon_io/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/dashboard/favicon_io/favicon-16x16.png">
<title>Document</title>
<script>
window.__authorizer__ = {{.data}}