fix: bug with authorizer url

This commit is contained in:
Lakhan Samani 2022-01-31 11:35:24 +05:30
parent 34a91f3195
commit 4e48320cf1
60 changed files with 156 additions and 148 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 birthdate
phone_number phone_number
picture picture
signup_methods
roles roles
created_at created_at
} }

View File

@ -61,6 +61,7 @@ interface userDataTypes {
birthdate: string; birthdate: string;
phone_number: string; phone_number: string;
picture: string; picture: string;
signup_methods: string;
roles: [string]; roles: [string];
created_at: number; created_at: number;
} }
@ -167,6 +168,8 @@ export default function Users() {
<Tr> <Tr>
<Th>Email</Th> <Th>Email</Th>
<Th>Created At</Th> <Th>Created At</Th>
<Th>Signup Methods</Th>
<Th>Roles</Th>
<Th>Verified</Th> <Th>Verified</Th>
<Th>Actions</Th> <Th>Actions</Th>
</Tr> </Tr>
@ -177,7 +180,11 @@ export default function Users() {
return ( return (
<Tr key={user.id} style={{ fontSize: 14 }}> <Tr key={user.id} style={{ fontSize: 14 }}>
<Td>{user.email}</Td> <Td>{user.email}</Td>
<Td>{dayjs(user.created_at).format('MMM DD, YYYY')}</Td> <Td>
{dayjs(user.created_at * 1000).format('MMM DD, YYYY')}
</Td>
<Td>{user.signup_methods}</Td>
<Td>{user.roles.join(', ')}</Td>
<Td> <Td>
<Tag <Tag
size="sm" size="sm"

View File

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

View File

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

View File

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

View File

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

View File

@ -8,6 +8,6 @@ type Session struct {
User User `json:"-" bson:"-"` User User `json:"-" bson:"-"`
UserAgent string `json:"user_agent" bson:"user_agent"` UserAgent string `json:"user_agent" bson:"user_agent"`
IP string `json:"ip" bson:"ip"` IP string `json:"ip" bson:"ip"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"` CreatedAt int64 `json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_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"` PhoneNumberVerifiedAt *int64 `json:"phone_number_verified_at" bson:"phone_number_verified_at"`
Picture *string `gorm:"type:text" json:"picture" bson:"picture"` Picture *string `gorm:"type:text" json:"picture" bson:"picture"`
Roles string `json:"roles" bson:"roles"` Roles string `json:"roles" bson:"roles"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"` CreatedAt int64 `json:"created_at" bson:"created_at"`
} }
func (user *User) AsAPIUser() *model.User { func (user *User) AsAPIUser() *model.User {

View File

@ -9,8 +9,8 @@ type VerificationRequest struct {
Token string `gorm:"type:text" json:"token" bson:"token"` Token string `gorm:"type:text" json:"token" bson:"token"`
Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"` Identifier string `gorm:"uniqueIndex:idx_email_identifier" json:"identifier" bson:"identifier"`
ExpiresAt int64 `json:"expires_at" bson:"expires_at"` ExpiresAt int64 `json:"expires_at" bson:"expires_at"`
CreatedAt int64 `gorm:"autoCreateTime" json:"created_at" bson:"created_at"` CreatedAt int64 `json:"created_at" bson:"created_at"`
UpdatedAt int64 `gorm:"autoUpdateTime" json:"updated_at" bson:"updated_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at"`
Email string `gorm:"uniqueIndex:idx_email_identifier" json:"email" bson:"email"` 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 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 { if result.Error != nil {
log.Println("error adding config:", result.Error) log.Println("error adding config:", result.Error)
return env, result.Error return env, result.Error

View File

@ -2,6 +2,7 @@ package sql
import ( import (
"log" "log"
"time"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
"github.com/google/uuid" "github.com/google/uuid"
@ -15,6 +16,8 @@ func (p *provider) AddSession(session models.Session) error {
} }
session.Key = session.ID session.Key = session.ID
session.CreatedAt = time.Now().Unix()
session.UpdatedAt = time.Now().Unix()
res := p.db.Clauses( res := p.db.Clauses(
clause.OnConflict{ clause.OnConflict{
DoNothing: true, 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.Roles = strings.Join(envstore.EnvInMemoryStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
} }
user.CreatedAt = time.Now().Unix()
user.UpdatedAt = time.Now().Unix()
user.Key = user.ID user.Key = user.ID
result := p.db.Clauses( result := p.db.Clauses(
clause.OnConflict{ clause.OnConflict{

View File

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

View File

@ -6,10 +6,10 @@ import (
) )
// SendForgotPasswordMail to send forgot password email // 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) resetPasswordUrl := envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyResetPasswordURL)
if resetPasswordUrl == "" { 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 // 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 // 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 // The receiver needs to be in slice as the receive supports multiple receiver
Receiver := []string{toEmail} Receiver := []string{toEmail}
@ -99,7 +99,7 @@ func SendVerificationMail(toEmail, token string) error {
data := make(map[string]interface{}, 3) data := make(map[string]interface{}, 3)
data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo) data["org_logo"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationLogo)
data["org_name"] = envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyOrganizationName) 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") message = addEmailTemplate(message, data, "verify_email.tmpl")
// bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message) // bodyMessage := sender.WriteHTMLEmail(Receiver, Subject, message)

5
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] == "" { if envData.StringEnv[constants.EnvKeyAppURL] == "" {
envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL) envData.StringEnv[constants.EnvKeyAppURL] = os.Getenv(constants.EnvKeyAppURL)
} }
@ -246,12 +244,11 @@ func InitEnv() {
trimVal := strings.TrimSpace(val) trimVal := strings.TrimSpace(val)
if trimVal != "" { if trimVal != "" {
roles = append(roles, trimVal) roles = append(roles, trimVal)
}
if utils.StringSliceContains(defaultRoleSplit, trimVal) { if utils.StringSliceContains(defaultRoleSplit, trimVal) {
defaultRoles = append(defaultRoles, trimVal) defaultRoles = append(defaultRoles, trimVal)
} }
} }
}
if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 { if len(roles) > 0 && len(defaultRoles) == 0 && len(defaultRolesEnv) > 0 {
panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`) panic(`Invalid DEFAULT_ROLE environment variable. It can be one from give ROLES environment variable value`)

View File

@ -6,7 +6,6 @@ require (
github.com/99designs/gqlgen v0.14.0 github.com/99designs/gqlgen v0.14.0
github.com/arangodb/go-driver v1.2.1 github.com/arangodb/go-driver v1.2.1
github.com/coreos/go-oidc/v3 v3.1.0 github.com/coreos/go-oidc/v3 v3.1.0
github.com/gin-contrib/location v0.0.2
github.com/gin-gonic/gin v1.7.2 github.com/gin-gonic/gin v1.7.2
github.com/go-playground/validator/v10 v10.8.0 // indirect github.com/go-playground/validator/v10 v10.8.0 // indirect
github.com/go-redis/redis/v8 v8.11.0 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.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 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= 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 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 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 h1:Tg03T9yM2xa8j6I3Z3oqLaQRSmKvxPd6g/2HJ6zICFA=
github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.7.2/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
@ -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/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 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= 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.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 h1:1kAa0fCrnpv+QYdkdcRzrRM7AyYs5o8+jZdJCz9xj6k=
github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk= 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 AdminSecret func(childComplexity int) int
AllowedOrigins func(childComplexity int) int AllowedOrigins func(childComplexity int) int
AppURL func(childComplexity int) int AppURL func(childComplexity int) int
AuthorizerURL func(childComplexity int) int
CookieName func(childComplexity int) int CookieName func(childComplexity int) int
CustomAccessTokenScript func(childComplexity int) int CustomAccessTokenScript func(childComplexity int) int
DatabaseName 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 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": case "Env.COOKIE_NAME":
if e.complexity.Env.CookieName == nil { if e.complexity.Env.CookieName == nil {
break break
@ -1215,7 +1207,6 @@ type Env {
JWT_TYPE: String JWT_TYPE: String
JWT_SECRET: String JWT_SECRET: String
ALLOWED_ORIGINS: [String!] ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String APP_URL: String
REDIS_URL: String REDIS_URL: String
COOKIE_NAME: String COOKIE_NAME: String
@ -1250,7 +1241,6 @@ input UpdateEnvInput {
JWT_TYPE: String JWT_TYPE: String
JWT_SECRET: String JWT_SECRET: String
ALLOWED_ORIGINS: [String!] ALLOWED_ORIGINS: [String!]
AUTHORIZER_URL: String
APP_URL: String APP_URL: String
REDIS_URL: String REDIS_URL: String
COOKIE_NAME: 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) 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) { func (ec *executionContext) _Env_APP_URL(ctx context.Context, field graphql.CollectedField, obj *model.Env) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -7094,14 +7052,6 @@ func (ec *executionContext) unmarshalInputUpdateEnvInput(ctx context.Context, ob
if err != nil { if err != nil {
return it, err 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": case "APP_URL":
var err error 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) out.Values[i] = ec._Env_JWT_SECRET(ctx, field, obj)
case "ALLOWED_ORIGINS": case "ALLOWED_ORIGINS":
out.Values[i] = ec._Env_ALLOWED_ORIGINS(ctx, field, obj) 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": case "APP_URL":
out.Values[i] = ec._Env_APP_URL(ctx, field, obj) out.Values[i] = ec._Env_APP_URL(ctx, field, obj)
case "REDIS_URL": case "REDIS_URL":

View File

@ -35,7 +35,6 @@ type Env struct {
JwtType *string `json:"JWT_TYPE"` JwtType *string `json:"JWT_TYPE"`
JwtSecret *string `json:"JWT_SECRET"` JwtSecret *string `json:"JWT_SECRET"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"` AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AuthorizerURL *string `json:"AUTHORIZER_URL"`
AppURL *string `json:"APP_URL"` AppURL *string `json:"APP_URL"`
RedisURL *string `json:"REDIS_URL"` RedisURL *string `json:"REDIS_URL"`
CookieName *string `json:"COOKIE_NAME"` CookieName *string `json:"COOKIE_NAME"`
@ -155,7 +154,6 @@ type UpdateEnvInput struct {
JwtType *string `json:"JWT_TYPE"` JwtType *string `json:"JWT_TYPE"`
JwtSecret *string `json:"JWT_SECRET"` JwtSecret *string `json:"JWT_SECRET"`
AllowedOrigins []string `json:"ALLOWED_ORIGINS"` AllowedOrigins []string `json:"ALLOWED_ORIGINS"`
AuthorizerURL *string `json:"AUTHORIZER_URL"`
AppURL *string `json:"APP_URL"` AppURL *string `json:"APP_URL"`
RedisURL *string `json:"REDIS_URL"` RedisURL *string `json:"REDIS_URL"`
CookieName *string `json:"COOKIE_NAME"` CookieName *string `json:"COOKIE_NAME"`

View File

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

View File

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

View File

@ -99,6 +99,11 @@ func OAuthCallbackHandler() gin.HandlerFunc {
user.SignupMethods = signupMethod user.SignupMethods = signupMethod
user.Password = existingUser.Password user.Password = existingUser.Password
if user.EmailVerifiedAt == nil {
now := time.Now().Unix()
user.EmailVerifiedAt = &now
}
// There multiple scenarios with roles here in social login // There multiple scenarios with roles here in social login
// 1. user has access to protected roles + roles and trying to 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. // 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 // OAuthLoginHandler set host in the oauth state that is useful for redirecting to oauth_callback
func OAuthLoginHandler() gin.HandlerFunc { func OAuthLoginHandler() gin.HandlerFunc {
return func(c *gin.Context) { return func(c *gin.Context) {
// TODO validate redirect URL hostname := utils.GetHost(c)
redirectURL := c.Query("redirectURL") redirectURL := c.Query("redirectURL")
roles := c.Query("roles") roles := c.Query("roles")
@ -56,7 +56,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
} }
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle) sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGoogle)
// during the init of OAuthProvider authorizer url might be empty // 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) url := oauth.OAuthProviders.GoogleConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodGithub: case constants.SignupMethodGithub:
@ -65,7 +65,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break break
} }
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodGithub) 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) url := oauth.OAuthProviders.GithubConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
case constants.SignupMethodFacebook: case constants.SignupMethodFacebook:
@ -74,7 +74,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
break break
} }
sessionstore.SetSocailLoginState(oauthStateString, constants.SignupMethodFacebook) 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) url := oauth.OAuthProviders.FacebookConfig.AuthCodeURL(oauthStateString)
c.Redirect(http.StatusTemporaryRedirect, url) c.Redirect(http.StatusTemporaryRedirect, url)
default: default:

View File

@ -2,22 +2,13 @@ package middlewares
import ( import (
"context" "context"
"log"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/envstore"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// GinContextToContextMiddleware is a middleware to add gin context in context // GinContextToContextMiddleware is a middleware to add gin context in context
func GinContextToContextMiddleware() gin.HandlerFunc { func GinContextToContextMiddleware() gin.HandlerFunc {
return func(c *gin.Context) { 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) ctx := context.WithValue(c.Request.Context(), "GinContextKey", c)
c.Request = c.Request.WithContext(ctx) c.Request = c.Request.WithContext(ctx)
c.Next() c.Next()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,6 +13,8 @@ import (
"github.com/authorizerdev/authorizer/server/db" "github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/envstore" "github.com/authorizerdev/authorizer/server/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "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/token"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
@ -115,6 +117,8 @@ func UpdateEnvResolver(ctx context.Context, params model.UpdateEnvInput) (*model
// Update local store // Update local store
envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData) envstore.EnvInMemoryStoreObj.UpdateEnvStore(updatedData)
sessionstore.InitSession()
oauth.InitOAuth()
// Fetch the current db store and update it // Fetch the current db store and update it
env, err := db.Provider.GetEnv() 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)) sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
cookie.DeleteCookie(gc) cookie.DeleteCookie(gc)
hostname := utils.GetHost(gc)
user.Email = newEmail user.Email = newEmail
user.EmailVerifiedAt = nil user.EmailVerifiedAt = nil
hasEmailChanged = true hasEmailChanged = true
// insert verification request // insert verification request
verificationType := constants.VerificationTypeUpdateEmail verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType) verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
if err != nil { if err != nil {
log.Println(`error generating token`, err) 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 // exec it as go routin so that we can reduce the api latency
go func() { 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)) sessionstore.DeleteAllUserSession(fmt.Sprintf("%v", user.ID))
cookie.DeleteCookie(gc) cookie.DeleteCookie(gc)
hostname := utils.GetHost(gc)
user.Email = newEmail user.Email = newEmail
user.EmailVerifiedAt = nil user.EmailVerifiedAt = nil
// insert verification request // insert verification request
verificationType := constants.VerificationTypeUpdateEmail verificationType := constants.VerificationTypeUpdateEmail
verificationToken, err := token.CreateVerificationToken(newEmail, verificationType) verificationToken, err := token.CreateVerificationToken(newEmail, verificationType, hostname)
if err != nil { if err != nil {
log.Println(`error generating token`, err) 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 // exec it as go routin so that we can reduce the api latency
go func() { 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) 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") return res, fmt.Errorf("invalid list of roles")
} }

View File

@ -3,14 +3,13 @@ package routes
import ( import (
"github.com/authorizerdev/authorizer/server/handlers" "github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares" "github.com/authorizerdev/authorizer/server/middlewares"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
) )
// InitRouter initializes gin router // InitRouter initializes gin router
func InitRouter() *gin.Engine { func InitRouter() *gin.Engine {
router := gin.Default() router := gin.Default()
router.Use(location.Default()) // router.Use(location.Default())
router.Use(middlewares.GinContextToContextMiddleware()) router.Use(middlewares.GinContextToContextMiddleware())
router.Use(middlewares.CORSMiddleware()) router.Use(middlewares.CORSMiddleware())
@ -25,14 +24,16 @@ func InitRouter() *gin.Engine {
// login page app related routes. // login page app related routes.
app := router.Group("/app") app := router.Group("/app")
{ {
app.Static("/favicon_io", "app/favicon_io")
app.Static("/build", "app/build") app.Static("/build", "app/build")
app.GET("/", handlers.AppHandler()) app.GET("/", handlers.AppHandler())
app.GET("/reset-password", handlers.AppHandler()) app.GET("/:page", handlers.AppHandler())
} }
// dashboard related routes // dashboard related routes
dashboard := router.Group("/dashboard") dashboard := router.Group("/dashboard")
{ {
dashboard.Static("/favicon_io", "dashboard/favicon_io")
dashboard.Static("/build", "dashboard/build") dashboard.Static("/build", "dashboard/build")
dashboard.GET("/", handlers.DashboardHandler()) dashboard.GET("/", handlers.DashboardHandler())
dashboard.GET("/:page", 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/envstore"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/google/uuid"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func adminSignupTests(t *testing.T, s TestSetup) { func adminSignupTests(t *testing.T, s TestSetup) {
t.Helper() 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) _, ctx := createContext(s)
_, err := resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{ _, err := resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
AdminSecret: "admin", AdminSecret: "admin",
@ -24,7 +23,7 @@ func adminSignupTests(t *testing.T, s TestSetup) {
envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "") envstore.EnvInMemoryStoreObj.UpdateEnvVariable(constants.StringStoreIdentifier, constants.EnvKeyAdminSecret, "")
_, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{ _, err = resolvers.AdminSignupResolver(ctx, model.AdminSignupInput{
AdminSecret: uuid.New().String(), AdminSecret: "admin123",
}) })
assert.Nil(t, err) assert.Nil(t, err)

View File

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

View File

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

View File

@ -14,7 +14,6 @@ import (
"github.com/authorizerdev/authorizer/server/handlers" "github.com/authorizerdev/authorizer/server/handlers"
"github.com/authorizerdev/authorizer/server/middlewares" "github.com/authorizerdev/authorizer/server/middlewares"
"github.com/authorizerdev/authorizer/server/sessionstore" "github.com/authorizerdev/authorizer/server/sessionstore"
"github.com/gin-contrib/location"
"github.com/gin-gonic/gin" "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.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() env.InitEnv()
sessionstore.InitSession() sessionstore.InitSession()
w := httptest.NewRecorder() w := httptest.NewRecorder()
c, r := gin.CreateTestContext(w) c, r := gin.CreateTestContext(w)
r.Use(location.Default())
r.Use(middlewares.GinContextToContextMiddleware()) r.Use(middlewares.GinContextToContextMiddleware())
r.Use(middlewares.CORSMiddleware()) r.Use(middlewares.CORSMiddleware())

View File

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

View File

@ -8,9 +8,9 @@ import (
) )
func TestGetHostName(t *testing.T) { 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" expectedHost := "test.herokuapp.com"
assert.Equal(t, host, expectedHost, "hostname should be equal") assert.Equal(t, host, expectedHost, "hostname should be equal")
@ -18,9 +18,9 @@ func TestGetHostName(t *testing.T) {
} }
func TestGetDomainName(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" want := "herokuapp.com"
assert.Equal(t, got, want, "domain name should be equal") assert.Equal(t, got, want, "domain name should be equal")

View File

@ -2,7 +2,6 @@ package token
import ( import (
"fmt" "fmt"
"log"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/cookie" "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))) err = bcrypt.CompareHashAndPassword([]byte(token), []byte(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyAdminSecret)))
log.Println("error comparing hash:", err)
if err != nil { if err != nil {
return "", fmt.Errorf(`unauthorized`) return "", fmt.Errorf(`unauthorized`)
} }

View File

@ -23,7 +23,7 @@ type CustomClaim struct {
} }
// CreateVerificationToken creates a verification JWT token // 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 := jwt.New(jwt.GetSigningMethod(envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtType)))
t.Claims = &CustomClaim{ t.Claims = &CustomClaim{
@ -31,7 +31,7 @@ func CreateVerificationToken(email string, tokenType string) (string, error) {
ExpiresAt: time.Now().Add(time.Minute * 30).Unix(), ExpiresAt: time.Now().Add(time.Minute * 30).Unix(),
}, },
tokenType, 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))) 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) 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 package utils
import ( import (
"log"
"net/url" "net/url"
"strings" "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 // GetHostName function returns hostname and port
func GetHostParts(uri string) (string, string) { func GetHostParts(uri string) (string, string) {
tempURI := uri tempURI := uri

View File

@ -2,8 +2,12 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>{{.data.organizationName}}</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <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> <title>Document</title>
<link rel="stylesheet" href="/app/build/index.css"> <link rel="stylesheet" href="/app/build/index.css">
<script> <script>

View File

@ -2,8 +2,12 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<title>Authorizer | Dashboard</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <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> <title>Document</title>
<script> <script>
window.__authorizer__ = {{.data}} window.__authorizer__ = {{.data}}