Compare commits

...

6 Commits

Author SHA1 Message Date
Lakhan Samani
8d145bd5fe Merge pull request #369 from authorizerdev/feat-add-validate-cookie-api
feat: add resolver to validate browser session
2023-07-12 22:13:47 +05:30
Lakhan Samani
6fa0ad1809 feat: add resolver to validate browser session 2023-07-12 22:12:17 +05:30
Lakhan Samani
07f71e883b Add comments for twillio 2023-07-11 14:49:16 +05:30
Lakhan Samani
6cef9064c3 Update provider template for sms verification 2023-07-11 14:48:37 +05:30
Lakhan Samani
9ae616b6b5 Merge pull request #365 from JokerQyou/patch-1
Fix wrong response_type parsed when missing response_mode
2023-06-30 18:10:31 +05:30
Joker_
356428ea02 Fix wrong response_type parsed when missing response_mode 2023-06-29 23:10:44 +08:00
19 changed files with 509 additions and 81 deletions

View File

@@ -181,8 +181,13 @@ const (
EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION" EnvKeyDisablePhoneVerification = "DISABLE_PHONE_VERIFICATION"
// Twilio env variables // Twilio env variables
// EnvKeyTwilioAPIKey key for env variable TWILIO_API_KEY
EnvKeyTwilioAPIKey = "TWILIO_API_KEY" EnvKeyTwilioAPIKey = "TWILIO_API_KEY"
// EnvKeyTwilioAPISecret key for env variable TWILIO_API_SECRET
EnvKeyTwilioAPISecret = "TWILIO_API_SECRET" EnvKeyTwilioAPISecret = "TWILIO_API_SECRET"
// EnvKeyTwilioAccountSID key for env variable TWILIO_ACCOUNT_SID
EnvKeyTwilioAccountSID = "TWILIO_ACCOUNT_SID" EnvKeyTwilioAccountSID = "TWILIO_ACCOUNT_SID"
// EnvKeyTwilioSenderFrom key for env variable TWILIO_SENDER_FROM
EnvKeyTwilioSenderFrom = "TWILIO_SENDER_FROM" EnvKeyTwilioSenderFrom = "TWILIO_SENDER_FROM"
) )

View File

@@ -2,14 +2,14 @@ package models
// Collections / Tables available for authorizer in the database // Collections / Tables available for authorizer in the database
type CollectionList struct { type CollectionList struct {
User string User string
VerificationRequest string VerificationRequest string
Session string Session string
Env string Env string
Webhook string Webhook string
WebhookLog string WebhookLog string
EmailTemplate string EmailTemplate string
OTP string OTP string
SMSVerificationRequest string SMSVerificationRequest string
} }
@@ -18,14 +18,14 @@ var (
Prefix = "authorizer_" Prefix = "authorizer_"
// Collections / Tables available for authorizer in the database (used for dbs other than gorm) // Collections / Tables available for authorizer in the database (used for dbs other than gorm)
Collections = CollectionList{ Collections = CollectionList{
User: Prefix + "users", User: Prefix + "users",
VerificationRequest: Prefix + "verification_requests", VerificationRequest: Prefix + "verification_requests",
Session: Prefix + "sessions", Session: Prefix + "sessions",
Env: Prefix + "env", Env: Prefix + "env",
Webhook: Prefix + "webhooks", Webhook: Prefix + "webhooks",
WebhookLog: Prefix + "webhook_logs", WebhookLog: Prefix + "webhook_logs",
EmailTemplate: Prefix + "email_templates", EmailTemplate: Prefix + "email_templates",
OTP: Prefix + "otps", OTP: Prefix + "otps",
SMSVerificationRequest: Prefix + "sms_verification_requests", SMSVerificationRequest: Prefix + "sms_verification_requests",
} }
) )

View File

@@ -2,10 +2,10 @@ package models
// SMS verification requests model for database // SMS verification requests model for database
type SMSVerificationRequest struct { type SMSVerificationRequest struct {
ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"` ID string `gorm:"primaryKey;type:char(36)" json:"_id" bson:"_id" cql:"id" dynamo:"id,hash"`
PhoneNumber string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number" index:"phone_number,hash"` PhoneNumber string `gorm:"unique" json:"phone_number" bson:"phone_number" cql:"phone_number" dynamo:"phone_number" index:"phone_number,hash"`
Code string `json:"code" bson:"code" cql:"code" dynamo:"code"` Code string `json:"code" bson:"code" cql:"code" dynamo:"code"`
CodeExpiresAt int64 `json:"code_expires_at" bson:"code_expires_at" cql:"code_expires_at" dynamo:"code_expires_at"` CodeExpiresAt int64 `json:"code_expires_at" bson:"code_expires_at" cql:"code_expires_at" dynamo:"code_expires_at"`
CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"` CreatedAt int64 `json:"created_at" bson:"created_at" cql:"created_at" dynamo:"created_at"`
UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"` UpdatedAt int64 `json:"updated_at" bson:"updated_at" cql:"updated_at" dynamo:"updated_at"`
} }

View File

@@ -4,20 +4,19 @@ import (
"context" "context"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
) )
// SMS verification Request // UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, sms_code *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return sms_code, nil return nil, nil
} }
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest return nil, nil
return &sms_verification_request, nil
} }
func(p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { // DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil return nil
} }

View File

@@ -4,20 +4,19 @@ import (
"context" "context"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
) )
// SMS verification Request // UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, sms_code *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return sms_code, nil return nil, nil
} }
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest return nil, nil
return &sms_verification_request, nil
} }
func(p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { // DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil return nil
} }

View File

@@ -4,20 +4,19 @@ import (
"context" "context"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
) )
// SMS verification Request // UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, sms_code *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return sms_code, nil return nil, nil
} }
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest return nil, nil
return &sms_verification_request, nil
} }
func(p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { // DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil return nil
} }

View File

@@ -4,20 +4,19 @@ import (
"context" "context"
"github.com/authorizerdev/authorizer/server/db/models" "github.com/authorizerdev/authorizer/server/db/models"
) )
// SMS verification Request // UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, sms_code *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return sms_code, nil return nil, nil
} }
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest return nil, nil
return &sms_verification_request, nil
} }
func(p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { // DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil return nil
} }

View File

@@ -10,41 +10,40 @@ import (
"go.mongodb.org/mongo-driver/mongo/options" "go.mongodb.org/mongo-driver/mongo/options"
) )
// SMS verification Request // UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
smsVerificationRequest, _ := p.GetCodeByPhone(ctx, smsRequest.PhoneNumber) smsVerificationRequest, err := p.GetCodeByPhone(ctx, smsRequest.PhoneNumber)
if err != nil {
return nil, err
}
// Boolean to check if we should create a new record or update the existing one
shouldCreate := false shouldCreate := false
if smsVerificationRequest == nil { if smsVerificationRequest == nil {
id := uuid.NewString() id := uuid.NewString()
smsVerificationRequest = &models.SMSVerificationRequest{ smsVerificationRequest = &models.SMSVerificationRequest{
ID: id, ID: id,
CreatedAt: time.Now().Unix(), CreatedAt: time.Now().Unix(),
Code: smsRequest.Code, Code: smsRequest.Code,
PhoneNumber: smsRequest.PhoneNumber, PhoneNumber: smsRequest.PhoneNumber,
CodeExpiresAt: smsRequest.CodeExpiresAt, CodeExpiresAt: smsRequest.CodeExpiresAt,
} }
shouldCreate = true shouldCreate = true
} }
smsVerificationRequest.UpdatedAt = time.Now().Unix() smsVerificationRequest.UpdatedAt = time.Now().Unix()
smsRequestCollection := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection()) smsRequestCollection := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection())
var err error
if shouldCreate { if shouldCreate {
_, err = smsRequestCollection.InsertOne(ctx, smsVerificationRequest) _, err = smsRequestCollection.InsertOne(ctx, smsVerificationRequest)
} else { } else {
_, err = smsRequestCollection.UpdateOne(ctx, bson.M{"phone_number": bson.M{"$eq": smsRequest.PhoneNumber}}, bson.M{"$set": smsVerificationRequest}, options.MergeUpdateOptions()) _, err = smsRequestCollection.UpdateOne(ctx, bson.M{"phone_number": bson.M{"$eq": smsRequest.PhoneNumber}}, bson.M{"$set": smsVerificationRequest}, options.MergeUpdateOptions())
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
return smsVerificationRequest, nil return smsVerificationRequest, nil
} }
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var smsVerificationRequest models.SMSVerificationRequest var smsVerificationRequest models.SMSVerificationRequest
@@ -58,6 +57,7 @@ func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*mod
return &smsVerificationRequest, nil return &smsVerificationRequest, nil
} }
// DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
smsVerificationRequests := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection()) smsVerificationRequests := p.db.Collection(models.Collections.SMSVerificationRequest, options.Collection())
_, err := smsVerificationRequests.DeleteOne(nil, bson.M{"_id": smsRequest.ID}, options.Delete()) _, err := smsVerificationRequests.DeleteOne(nil, bson.M{"_id": smsRequest.ID}, options.Delete())

View File

@@ -0,0 +1,22 @@
package provider_template
import (
"context"
"github.com/authorizerdev/authorizer/server/db/models"
)
// UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
return nil, nil
}
// DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
return nil
}

View File

@@ -85,10 +85,10 @@ type Provider interface {
// DeleteOTP to delete otp // DeleteOTP to delete otp
DeleteOTP(ctx context.Context, otp *models.OTP) error DeleteOTP(ctx context.Context, otp *models.OTP) error
// Upsert SMS code request // UpsertSMSRequest adds/updates SMS verification request
UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error)
// Get sms code by phone number // GetCodeByPhone to get code for a given phone number
GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error)
// Delete sms // DeleteSMSRequest to delete SMS verification request
DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error
} }

View File

@@ -9,27 +9,24 @@ import (
"gorm.io/gorm/clause" "gorm.io/gorm/clause"
) )
// SMS verification Request // UpsertSMSRequest adds/updates SMS verification request
func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) { func (p *provider) UpsertSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) (*models.SMSVerificationRequest, error) {
if smsRequest.ID == "" { if smsRequest.ID == "" {
smsRequest.ID = uuid.New().String() smsRequest.ID = uuid.New().String()
} }
smsRequest.CreatedAt = time.Now().Unix() smsRequest.CreatedAt = time.Now().Unix()
smsRequest.UpdatedAt = time.Now().Unix() smsRequest.UpdatedAt = time.Now().Unix()
res := p.db.Clauses(clause.OnConflict{ res := p.db.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "phone_number"}}, Columns: []clause.Column{{Name: "phone_number"}},
DoUpdates: clause.AssignmentColumns([]string{"code", "code_expires_at"}), DoUpdates: clause.AssignmentColumns([]string{"code", "code_expires_at", "updated_at"}),
}).Create(smsRequest) }).Create(smsRequest)
if res.Error != nil { if res.Error != nil {
return nil, res.Error return nil, res.Error
} }
return smsRequest, nil return smsRequest, nil
} }
// GetOTPByEmail to get otp for a given email address // GetCodeByPhone to get code for a given phone number
func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) { func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*models.SMSVerificationRequest, error) {
var sms_verification_request models.SMSVerificationRequest var sms_verification_request models.SMSVerificationRequest
@@ -40,7 +37,8 @@ func (p *provider) GetCodeByPhone(ctx context.Context, phoneNumber string) (*mod
return &sms_verification_request, nil return &sms_verification_request, nil
} }
func(p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error { // DeleteSMSRequest to delete SMS verification request
func (p *provider) DeleteSMSRequest(ctx context.Context, smsRequest *models.SMSVerificationRequest) error {
result := p.db.Delete(&models.SMSVerificationRequest{ result := p.db.Delete(&models.SMSVerificationRequest{
ID: smsRequest.ID, ID: smsRequest.ID,
}) })

View File

@@ -219,6 +219,7 @@ type ComplexityRoot struct {
User func(childComplexity int, params model.GetUserRequest) int User func(childComplexity int, params model.GetUserRequest) int
Users func(childComplexity int, params *model.PaginatedInput) int Users func(childComplexity int, params *model.PaginatedInput) int
ValidateJwtToken func(childComplexity int, params model.ValidateJWTTokenInput) int ValidateJwtToken func(childComplexity int, params model.ValidateJWTTokenInput) int
ValidateSession func(childComplexity int, params *model.ValidateSessionInput) int
VerificationRequests func(childComplexity int, params *model.PaginatedInput) int VerificationRequests func(childComplexity int, params *model.PaginatedInput) int
Webhook func(childComplexity int, params model.WebhookRequest) int Webhook func(childComplexity int, params model.WebhookRequest) int
WebhookLogs func(childComplexity int, params *model.ListWebhookLogRequest) int WebhookLogs func(childComplexity int, params *model.ListWebhookLogRequest) int
@@ -275,6 +276,10 @@ type ComplexityRoot struct {
IsValid func(childComplexity int) int IsValid func(childComplexity int) int
} }
ValidateSessionResponse struct {
IsValid func(childComplexity int) int
}
VerificationRequest struct { VerificationRequest struct {
CreatedAt func(childComplexity int) int CreatedAt func(childComplexity int) int
Email func(childComplexity int) int Email func(childComplexity int) int
@@ -363,6 +368,7 @@ type QueryResolver interface {
Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error)
Profile(ctx context.Context) (*model.User, error) Profile(ctx context.Context) (*model.User, error)
ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error) ValidateJwtToken(ctx context.Context, params model.ValidateJWTTokenInput) (*model.ValidateJWTTokenResponse, error)
ValidateSession(ctx context.Context, params *model.ValidateSessionInput) (*model.ValidateSessionResponse, error)
Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error)
User(ctx context.Context, params model.GetUserRequest) (*model.User, error) User(ctx context.Context, params model.GetUserRequest) (*model.User, error)
VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error) VerificationRequests(ctx context.Context, params *model.PaginatedInput) (*model.VerificationRequests, error)
@@ -1572,6 +1578,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.ValidateJwtToken(childComplexity, args["params"].(model.ValidateJWTTokenInput)), true return e.complexity.Query.ValidateJwtToken(childComplexity, args["params"].(model.ValidateJWTTokenInput)), true
case "Query.validate_session":
if e.complexity.Query.ValidateSession == nil {
break
}
args, err := ec.field_Query_validate_session_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Query.ValidateSession(childComplexity, args["params"].(*model.ValidateSessionInput)), true
case "Query._verification_requests": case "Query._verification_requests":
if e.complexity.Query.VerificationRequests == nil { if e.complexity.Query.VerificationRequests == nil {
break break
@@ -1844,6 +1862,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.ValidateJWTTokenResponse.IsValid(childComplexity), true return e.complexity.ValidateJWTTokenResponse.IsValid(childComplexity), true
case "ValidateSessionResponse.is_valid":
if e.complexity.ValidateSessionResponse.IsValid == nil {
break
}
return e.complexity.ValidateSessionResponse.IsValid(childComplexity), true
case "VerificationRequest.created_at": case "VerificationRequest.created_at":
if e.complexity.VerificationRequest.CreatedAt == nil { if e.complexity.VerificationRequest.CreatedAt == nil {
break break
@@ -2093,6 +2118,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputUpdateUserInput, ec.unmarshalInputUpdateUserInput,
ec.unmarshalInputUpdateWebhookRequest, ec.unmarshalInputUpdateWebhookRequest,
ec.unmarshalInputValidateJWTTokenInput, ec.unmarshalInputValidateJWTTokenInput,
ec.unmarshalInputValidateSessionInput,
ec.unmarshalInputVerifyEmailInput, ec.unmarshalInputVerifyEmailInput,
ec.unmarshalInputVerifyMobileRequest, ec.unmarshalInputVerifyMobileRequest,
ec.unmarshalInputVerifyOTPRequest, ec.unmarshalInputVerifyOTPRequest,
@@ -2341,6 +2367,10 @@ type ValidateJWTTokenResponse {
claims: Map claims: Map
} }
type ValidateSessionResponse {
is_valid: Boolean!
}
type GenerateJWTKeysResponse { type GenerateJWTKeysResponse {
secret: String secret: String
public_key: String public_key: String
@@ -2633,6 +2663,11 @@ input ValidateJWTTokenInput {
roles: [String!] roles: [String!]
} }
input ValidateSessionInput {
cookie: String!
roles: [String!]
}
input GenerateJWTKeysInput { input GenerateJWTKeysInput {
type: String! type: String!
} }
@@ -2755,6 +2790,7 @@ type Query {
session(params: SessionQueryInput): AuthResponse! session(params: SessionQueryInput): AuthResponse!
profile: User! profile: User!
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse! validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
validate_session(params: ValidateSessionInput): ValidateSessionResponse!
# admin only apis # admin only apis
_users(params: PaginatedInput): Users! _users(params: PaginatedInput): Users!
_user(params: GetUserRequest!): User! _user(params: GetUserRequest!): User!
@@ -3374,6 +3410,21 @@ func (ec *executionContext) field_Query_validate_jwt_token_args(ctx context.Cont
return args, nil return args, nil
} }
func (ec *executionContext) field_Query_validate_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error
args := map[string]interface{}{}
var arg0 *model.ValidateSessionInput
if tmp, ok := rawArgs["params"]; ok {
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
arg0, err = ec.unmarshalOValidateSessionInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionInput(ctx, tmp)
if err != nil {
return nil, err
}
}
args["params"] = arg0
return args, nil
}
func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { func (ec *executionContext) field___Type_enumValues_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
var err error var err error
args := map[string]interface{}{} args := map[string]interface{}{}
@@ -10159,6 +10210,65 @@ func (ec *executionContext) fieldContext_Query_validate_jwt_token(ctx context.Co
return fc, nil return fc, nil
} }
func (ec *executionContext) _Query_validate_session(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query_validate_session(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Query().ValidateSession(rctx, fc.Args["params"].(*model.ValidateSessionInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*model.ValidateSessionResponse)
fc.Result = res
return ec.marshalNValidateSessionResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionResponse(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Query_validate_session(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Query",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "is_valid":
return ec.fieldContext_ValidateSessionResponse_is_valid(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type ValidateSessionResponse", field.Name)
},
}
defer func() {
if r := recover(); r != nil {
err = ec.Recover(ctx, r)
ec.Error(ctx, err)
}
}()
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Query_validate_session_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return
}
return fc, nil
}
func (ec *executionContext) _Query__users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { func (ec *executionContext) _Query__users(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Query__users(ctx, field) fc, err := ec.fieldContext_Query__users(ctx, field)
if err != nil { if err != nil {
@@ -12381,6 +12491,50 @@ func (ec *executionContext) fieldContext_ValidateJWTTokenResponse_claims(ctx con
return fc, nil return fc, nil
} }
func (ec *executionContext) _ValidateSessionResponse_is_valid(ctx context.Context, field graphql.CollectedField, obj *model.ValidateSessionResponse) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_ValidateSessionResponse_is_valid(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) {
ctx = rctx // use context from middleware stack in children
return obj.IsValid, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_ValidateSessionResponse_is_valid(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "ValidateSessionResponse",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) { func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_VerificationRequest_id(ctx, field) fc, err := ec.fieldContext_VerificationRequest_id(ctx, field)
if err != nil { if err != nil {
@@ -17555,6 +17709,42 @@ func (ec *executionContext) unmarshalInputValidateJWTTokenInput(ctx context.Cont
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputValidateSessionInput(ctx context.Context, obj interface{}) (model.ValidateSessionInput, error) {
var it model.ValidateSessionInput
asMap := map[string]interface{}{}
for k, v := range obj.(map[string]interface{}) {
asMap[k] = v
}
fieldsInOrder := [...]string{"cookie", "roles"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "cookie":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("cookie"))
it.Cookie, err = ec.unmarshalNString2string(ctx, v)
if err != nil {
return it, err
}
case "roles":
var err error
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles"))
it.Roles, err = ec.unmarshalOString2ᚕstringᚄ(ctx, v)
if err != nil {
return it, err
}
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context, obj interface{}) (model.VerifyEmailInput, error) { func (ec *executionContext) unmarshalInputVerifyEmailInput(ctx context.Context, obj interface{}) (model.VerifyEmailInput, error) {
var it model.VerifyEmailInput var it model.VerifyEmailInput
asMap := map[string]interface{}{} asMap := map[string]interface{}{}
@@ -18866,6 +19056,29 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc) return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc)
} }
out.Concurrently(i, func() graphql.Marshaler {
return rrm(innerCtx)
})
case "validate_session":
field := field
innerFunc := func(ctx context.Context) (res graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
}
}()
res = ec._Query_validate_session(ctx, field)
if res == graphql.Null {
atomic.AddUint32(&invalids, 1)
}
return res
}
rrm := func(ctx context.Context) graphql.Marshaler {
return ec.OperationContext.RootResolverMiddleware(ctx, innerFunc)
}
out.Concurrently(i, func() graphql.Marshaler { out.Concurrently(i, func() graphql.Marshaler {
return rrm(innerCtx) return rrm(innerCtx)
}) })
@@ -19395,6 +19608,34 @@ func (ec *executionContext) _ValidateJWTTokenResponse(ctx context.Context, sel a
return out return out
} }
var validateSessionResponseImplementors = []string{"ValidateSessionResponse"}
func (ec *executionContext) _ValidateSessionResponse(ctx context.Context, sel ast.SelectionSet, obj *model.ValidateSessionResponse) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, validateSessionResponseImplementors)
out := graphql.NewFieldSet(fields)
var invalids uint32
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("ValidateSessionResponse")
case "is_valid":
out.Values[i] = ec._ValidateSessionResponse_is_valid(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch()
if invalids > 0 {
return graphql.Null
}
return out
}
var verificationRequestImplementors = []string{"VerificationRequest"} var verificationRequestImplementors = []string{"VerificationRequest"}
func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler { func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler {
@@ -20470,6 +20711,20 @@ func (ec *executionContext) marshalNValidateJWTTokenResponse2ᚖgithubᚗcomᚋa
return ec._ValidateJWTTokenResponse(ctx, sel, v) return ec._ValidateJWTTokenResponse(ctx, sel, v)
} }
func (ec *executionContext) marshalNValidateSessionResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionResponse(ctx context.Context, sel ast.SelectionSet, v model.ValidateSessionResponse) graphql.Marshaler {
return ec._ValidateSessionResponse(ctx, sel, &v)
}
func (ec *executionContext) marshalNValidateSessionResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionResponse(ctx context.Context, sel ast.SelectionSet, v *model.ValidateSessionResponse) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._ValidateSessionResponse(ctx, sel, v)
}
func (ec *executionContext) marshalNVerificationRequest2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerificationRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.VerificationRequest) graphql.Marshaler { func (ec *executionContext) marshalNVerificationRequest2ᚕᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐVerificationRequestᚄ(ctx context.Context, sel ast.SelectionSet, v []*model.VerificationRequest) graphql.Marshaler {
ret := make(graphql.Array, len(v)) ret := make(graphql.Array, len(v))
var wg sync.WaitGroup var wg sync.WaitGroup
@@ -21158,6 +21413,14 @@ func (ec *executionContext) marshalOUser2ᚖgithubᚗcomᚋauthorizerdevᚋautho
return ec._User(ctx, sel, v) return ec._User(ctx, sel, v)
} }
func (ec *executionContext) unmarshalOValidateSessionInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidateSessionInput(ctx context.Context, v interface{}) (*model.ValidateSessionInput, error) {
if v == nil {
return nil, nil
}
res, err := ec.unmarshalInputValidateSessionInput(ctx, v)
return &res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler { func (ec *executionContext) marshalO__EnumValue2ᚕgithubᚗcomᚋ99designsᚋgqlgenᚋgraphqlᚋintrospectionᚐEnumValueᚄ(ctx context.Context, sel ast.SelectionSet, v []introspection.EnumValue) graphql.Marshaler {
if v == nil { if v == nil {
return graphql.Null return graphql.Null

View File

@@ -120,7 +120,6 @@ type Env struct {
AdminCookieSecure bool `json:"ADMIN_COOKIE_SECURE"` AdminCookieSecure bool `json:"ADMIN_COOKIE_SECURE"`
DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE"` DefaultAuthorizeResponseType *string `json:"DEFAULT_AUTHORIZE_RESPONSE_TYPE"`
DefaultAuthorizeResponseMode *string `json:"DEFAULT_AUTHORIZE_RESPONSE_MODE"` DefaultAuthorizeResponseMode *string `json:"DEFAULT_AUTHORIZE_RESPONSE_MODE"`
SmsCodeExpiryTime *string `json:"SMS_CODE_EXPIRY_TIME"`
} }
type Error struct { type Error struct {
@@ -456,6 +455,15 @@ type ValidateJWTTokenResponse struct {
Claims map[string]interface{} `json:"claims"` Claims map[string]interface{} `json:"claims"`
} }
type ValidateSessionInput struct {
Cookie string `json:"cookie"`
Roles []string `json:"roles"`
}
type ValidateSessionResponse struct {
IsValid bool `json:"is_valid"`
}
type VerificationRequest struct { type VerificationRequest struct {
ID string `json:"id"` ID string `json:"id"`
Identifier *string `json:"identifier"` Identifier *string `json:"identifier"`

View File

@@ -182,6 +182,10 @@ type ValidateJWTTokenResponse {
claims: Map claims: Map
} }
type ValidateSessionResponse {
is_valid: Boolean!
}
type GenerateJWTKeysResponse { type GenerateJWTKeysResponse {
secret: String secret: String
public_key: String public_key: String
@@ -474,6 +478,11 @@ input ValidateJWTTokenInput {
roles: [String!] roles: [String!]
} }
input ValidateSessionInput {
cookie: String!
roles: [String!]
}
input GenerateJWTKeysInput { input GenerateJWTKeysInput {
type: String! type: String!
} }
@@ -596,6 +605,7 @@ type Query {
session(params: SessionQueryInput): AuthResponse! session(params: SessionQueryInput): AuthResponse!
profile: User! profile: User!
validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse! validate_jwt_token(params: ValidateJWTTokenInput!): ValidateJWTTokenResponse!
validate_session(params: ValidateSessionInput): ValidateSessionResponse!
# admin only apis # admin only apis
_users(params: PaginatedInput): Users! _users(params: PaginatedInput): Users!
_user(params: GetUserRequest!): User! _user(params: GetUserRequest!): User!

View File

@@ -191,6 +191,11 @@ func (r *queryResolver) ValidateJwtToken(ctx context.Context, params model.Valid
return resolvers.ValidateJwtTokenResolver(ctx, params) return resolvers.ValidateJwtTokenResolver(ctx, params)
} }
// ValidateSession is the resolver for the validate_session field.
func (r *queryResolver) ValidateSession(ctx context.Context, params *model.ValidateSessionInput) (*model.ValidateSessionResponse, error) {
return resolvers.ValidateSessionResolver(ctx, params)
}
// Users is the resolver for the _users field. // Users is the resolver for the _users field.
func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) { func (r *queryResolver) Users(ctx context.Context, params *model.PaginatedInput) (*model.Users, error) {
return resolvers.UsersResolver(ctx, params) return resolvers.UsersResolver(ctx, params)

View File

@@ -84,9 +84,9 @@ func AuthorizeHandler() gin.HandlerFunc {
if responseMode == "" { if responseMode == "" {
if val, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultAuthorizeResponseMode); err == nil { if val, err := memorystore.Provider.GetStringStoreEnvVariable(constants.EnvKeyDefaultAuthorizeResponseMode); err == nil {
responseType = val responseMode = val
} else { } else {
responseType = constants.ResponseModeQuery responseMode = constants.ResponseModeQuery
} }
} }

View File

@@ -0,0 +1,59 @@
package resolvers
import (
"context"
"errors"
"fmt"
"github.com/authorizerdev/authorizer/server/cookie"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/token"
"github.com/authorizerdev/authorizer/server/utils"
log "github.com/sirupsen/logrus"
)
// ValidateSessionResolver is used to validate a cookie session without its rotation
func ValidateSessionResolver(ctx context.Context, params *model.ValidateSessionInput) (*model.ValidateSessionResponse, error) {
gc, err := utils.GinContextFromContext(ctx)
if err != nil {
log.Debug("Failed to get GinContext: ", err)
return nil, err
}
sessionToken := params.Cookie
if sessionToken == "" {
sessionToken, err = cookie.GetSession(gc)
if err != nil {
log.Debug("Failed to get session token: ", err)
return nil, errors.New("unauthorized")
}
}
claims, err := token.ValidateBrowserSession(gc, sessionToken)
if err != nil {
log.Debug("Failed to validate session token", err)
return nil, errors.New("unauthorized")
}
userID := claims.Subject
log := log.WithFields(log.Fields{
"user_id": userID,
})
_, err = db.Provider.GetUserByID(ctx, userID)
if err != nil {
return nil, err
}
// refresh token has "roles" as claim
claimRoleInterface := claims.Roles
claimRoles := []string{}
claimRoles = append(claimRoles, claimRoleInterface...)
if params != nil && params.Roles != nil && len(params.Roles) > 0 {
for _, v := range params.Roles {
if !utils.StringSliceContains(claimRoles, v) {
log.Debug("User does not have required role: ", claimRoles, v)
return nil, fmt.Errorf(`unauthorized`)
}
}
}
return &model.ValidateSessionResponse{
IsValid: true,
}, nil
}

View File

@@ -136,6 +136,7 @@ func TestResolvers(t *testing.T) {
verifyOTPTest(t, s) verifyOTPTest(t, s)
resendOTPTest(t, s) resendOTPTest(t, s)
verifyMobileTest(t, s) verifyMobileTest(t, s)
validateSessionTests(t, s)
updateAllUsersTest(t, s) updateAllUsersTest(t, s)
webhookLogsTest(t, s) // get logs after above resolver tests are done webhookLogsTest(t, s) // get logs after above resolver tests are done

View File

@@ -0,0 +1,61 @@
package test
import (
"fmt"
"strings"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/db"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/memorystore"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/token"
"github.com/stretchr/testify/assert"
)
// ValidateSessionTests tests all the validate session resolvers
func validateSessionTests(t *testing.T, s TestSetup) {
t.Helper()
t.Run(`should validate session`, func(t *testing.T) {
req, ctx := createContext(s)
email := "validate_session." + s.TestInfo.Email
resolvers.SignupResolver(ctx, model.SignUpInput{
Email: email,
Password: s.TestInfo.Password,
ConfirmPassword: s.TestInfo.Password,
})
_, err := resolvers.ValidateSessionResolver(ctx, &model.ValidateSessionInput{})
assert.NotNil(t, err, "unauthorized")
verificationRequest, err := db.Provider.GetVerificationRequestByEmail(ctx, email, constants.VerificationTypeBasicAuthSignup)
assert.NoError(t, err)
assert.NotNil(t, verificationRequest)
verifyRes, err := resolvers.VerifyEmailResolver(ctx, model.VerifyEmailInput{
Token: verificationRequest.Token,
})
assert.NoError(t, err)
assert.NotNil(t, verifyRes)
accessToken := *verifyRes.AccessToken
assert.NotEmpty(t, accessToken)
claims, err := token.ParseJWTToken(accessToken)
assert.NoError(t, err)
assert.NotEmpty(t, claims)
sessionKey := constants.AuthRecipeMethodBasicAuth + ":" + verifyRes.User.ID
sessionToken, err := memorystore.Provider.GetUserSession(sessionKey, constants.TokenTypeSessionToken+"_"+claims["nonce"].(string))
assert.NoError(t, err)
assert.NotEmpty(t, sessionToken)
cookie := fmt.Sprintf("%s=%s;", constants.AppCookieName+"_session", sessionToken)
cookie = strings.TrimSuffix(cookie, ";")
res, err := resolvers.ValidateSessionResolver(ctx, &model.ValidateSessionInput{
Cookie: sessionToken,
})
assert.Nil(t, err)
assert.True(t, res.IsValid)
req.Header.Set("Cookie", cookie)
res, err = resolvers.ValidateSessionResolver(ctx, &model.ValidateSessionInput{})
assert.Nil(t, err)
assert.True(t, res.IsValid)
cleanData(email)
})
}