diff --git a/app/package-lock.json b/app/package-lock.json index e3c6acb..7434355 100644 --- a/app/package-lock.json +++ b/app/package-lock.json @@ -5,19 +5,19 @@ "requires": true, "dependencies": { "@authorizerdev/authorizer-js": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.0.tgz", - "integrity": "sha512-T2gurEYEP1T56j9IwDIWP1PsELoWcU7TBl5G0UsMqFQhKo7T6p2hM634Z7PS1yLegU3aihuvHGI0C0n4xSo0VQ==", + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-js/-/authorizer-js-0.2.1.tgz", + "integrity": "sha512-5lQlh+nc5xTsPongfTyCSX24A1WESu/BjhmZwUNuScEOGady0qPoDHE3RBf46dpi5v05wbHCDN1IFEalX5zssQ==", "requires": { "node-fetch": "^2.6.1" } }, "@authorizerdev/authorizer-react": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.0.tgz", - "integrity": "sha512-ydE7xrP3cqeTtU923bK0+OIB1fnL0VHnbThcNa41n89XUPV3VBhZ23gxMg90nqon8JFE5g2TNz7+/qBCOQ7aZw==", + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.4.3.tgz", + "integrity": "sha512-o/wWe9zZ3ARYdjbDfhGfvOxe1YQrE1YQ+UN9pcq85YSDkbfBkOfcnJ4YxlxWdL0Obd/ErDIeQ3vskyrfvRf3sA==", "requires": { - "@authorizerdev/authorizer-js": "^0.2.0", + "@authorizerdev/authorizer-js": "^0.2.1", "final-form": "^4.20.2", "react-final-form": "^6.5.3", "styled-components": "^5.3.0" @@ -32,11 +32,11 @@ } }, "@babel/generator": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.7.tgz", - "integrity": "sha512-/ST3Sg8MLGY5HVYmrjOgL60ENux/HfO/CsUh7y4MalThufhE/Ff/6EibFDHi4jiDCaWfJKoqbE6oTh21c5hrRg==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.16.8.tgz", + "integrity": "sha512-1ojZwE9+lOXzcWdWmO6TbUzDfqLD39CmEhN8+2cX9XkDo5yW1OpgfejfliysR2AWLpMamTiOiAp/mtroaymhpw==", "requires": { - "@babel/types": "^7.16.7", + "@babel/types": "^7.16.8", "jsesc": "^2.5.1", "source-map": "^0.5.0" } @@ -105,9 +105,9 @@ "integrity": "sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw==" }, "@babel/highlight": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.7.tgz", - "integrity": "sha512-aKpPMfLvGO3Q97V0qhw/V2SWNWlwfJknuwAunU7wZLSfrM4xTBvg7E5opUVi1kJTBKihE38CPg4nBiqX83PWYw==", + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.16.10.tgz", + "integrity": "sha512-5FnTQLSLswEj6IkgVw5KusNUUFY9ZGqe/TRFnP/BKYHYgfh7tc+C7mwiy95/yNP7Dh9x580Vv8r7u7ZfTBFxdw==", "requires": { "@babel/helper-validator-identifier": "^7.16.7", "chalk": "^2.0.0", @@ -115,9 +115,9 @@ } }, "@babel/parser": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.7.tgz", - "integrity": "sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA==" + "version": "7.16.12", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.16.12.tgz", + "integrity": "sha512-VfaV15po8RiZssrkPweyvbGVSe4x2y+aciFCgn0n0/SJMR22cwofRV1mtnJQYcSB1wUTaA/X1LnA3es66MCO5A==" }, "@babel/runtime": { "version": "7.14.8", @@ -138,26 +138,26 @@ } }, "@babel/traverse": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.7.tgz", - "integrity": "sha512-8KWJPIb8c2VvY8AJrydh6+fVRo2ODx1wYBU2398xJVq0JomuLBZmVQzLPBblJgHIGYG4znCpUZUZ0Pt2vdmVYQ==", + "version": "7.16.10", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.16.10.tgz", + "integrity": "sha512-yzuaYXoRJBGMlBhsMJoUW7G1UmSb/eXr/JHYM/MsOJgavJibLwASijW7oXBdw3NQ6T0bW7Ty5P/VarOs9cHmqw==", "requires": { "@babel/code-frame": "^7.16.7", - "@babel/generator": "^7.16.7", + "@babel/generator": "^7.16.8", "@babel/helper-environment-visitor": "^7.16.7", "@babel/helper-function-name": "^7.16.7", "@babel/helper-hoist-variables": "^7.16.7", "@babel/helper-split-export-declaration": "^7.16.7", - "@babel/parser": "^7.16.7", - "@babel/types": "^7.16.7", + "@babel/parser": "^7.16.10", + "@babel/types": "^7.16.8", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.7.tgz", - "integrity": "sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg==", + "version": "7.16.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.16.8.tgz", + "integrity": "sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg==", "requires": { "@babel/helper-validator-identifier": "^7.16.7", "to-fast-properties": "^2.0.0" @@ -420,9 +420,9 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "node-fetch": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz", - "integrity": "sha512-Z8/6vRlTUChSdIgMa51jxQ4lrw/Jy5SOW10ObaA47/RElsAN2c5Pn8bTgFGWn/ibwzXTE8qwr1Yzx28vsecXEA==", + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", "requires": { "whatwg-url": "^5.0.0" } diff --git a/server/graph/generated/generated.go b/server/graph/generated/generated.go index cf5ffba..adcd3c4 100644 --- a/server/graph/generated/generated.go +++ b/server/graph/generated/generated.go @@ -119,9 +119,10 @@ type ComplexityRoot struct { Query struct { AdminSession func(childComplexity int) int Env func(childComplexity int) int + IsValidJwt func(childComplexity int, params *model.IsValidJWTQueryInput) int Meta func(childComplexity int) int Profile func(childComplexity int) int - Session func(childComplexity int, roles []string) int + Session func(childComplexity int, params *model.SessionQueryInput) int Users func(childComplexity int) int VerificationRequests func(childComplexity int) int } @@ -150,6 +151,11 @@ type ComplexityRoot struct { UpdatedAt func(childComplexity int) int } + ValidJWTResponse struct { + Message func(childComplexity int) int + Valid func(childComplexity int) int + } + VerificationRequest struct { CreatedAt func(childComplexity int) int Email func(childComplexity int) int @@ -180,7 +186,8 @@ type MutationResolver interface { } type QueryResolver interface { Meta(ctx context.Context) (*model.Meta, error) - Session(ctx context.Context, roles []string) (*model.AuthResponse, error) + Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) + IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) Profile(ctx context.Context) (*model.User, error) Users(ctx context.Context) ([]*model.User, error) VerificationRequests(ctx context.Context) ([]*model.VerificationRequest, error) @@ -688,6 +695,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.Query.Env(childComplexity), true + case "Query.is_valid_jwt": + if e.complexity.Query.IsValidJwt == nil { + break + } + + args, err := ec.field_Query_is_valid_jwt_args(context.TODO(), rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.Query.IsValidJwt(childComplexity, args["params"].(*model.IsValidJWTQueryInput)), true + case "Query.meta": if e.complexity.Query.Meta == nil { break @@ -712,7 +731,7 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return 0, false } - return e.complexity.Query.Session(childComplexity, args["roles"].([]string)), true + return e.complexity.Query.Session(childComplexity, args["params"].(*model.SessionQueryInput)), true case "Query._users": if e.complexity.Query.Users == nil { @@ -854,6 +873,20 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in return e.complexity.User.UpdatedAt(childComplexity), true + case "ValidJWTResponse.message": + if e.complexity.ValidJWTResponse.Message == nil { + break + } + + return e.complexity.ValidJWTResponse.Message(childComplexity), true + + case "ValidJWTResponse.valid": + if e.complexity.ValidJWTResponse.Valid == nil { + break + } + + return e.complexity.ValidJWTResponse.Valid(childComplexity), true + case "VerificationRequest.created_at": if e.complexity.VerificationRequest.CreatedAt == nil { break @@ -1031,6 +1064,11 @@ type Response { message: String! } +type ValidJWTResponse { + valid: Boolean! + message: String! +} + type Env { ADMIN_SECRET: String SMTP_HOST: String @@ -1184,6 +1222,15 @@ input MagicLinkLoginInput { roles: [String!] } +input SessionQueryInput { + roles: [String!] +} + +input IsValidJWTQueryInput { + jwt: String! + roles: [String!] +} + type Mutation { signup(params: SignUpInput!): AuthResponse! login(params: LoginInput!): AuthResponse! @@ -1205,7 +1252,8 @@ type Mutation { type Query { meta: Meta! - session(roles: [String!]): AuthResponse! + session(params: SessionQueryInput): AuthResponse! + is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse! profile: User! # admin only apis _users: [User!]! @@ -1431,18 +1479,33 @@ func (ec *executionContext) field_Query___type_args(ctx context.Context, rawArgs return args, nil } -func (ec *executionContext) field_Query_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { +func (ec *executionContext) field_Query_is_valid_jwt_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { var err error args := map[string]interface{}{} - var arg0 []string - if tmp, ok := rawArgs["roles"]; ok { - ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("roles")) - arg0, err = ec.unmarshalOString2ᚕstringᚄ(ctx, tmp) + var arg0 *model.IsValidJWTQueryInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx, tmp) if err != nil { return nil, err } } - args["roles"] = arg0 + args["params"] = arg0 + return args, nil +} + +func (ec *executionContext) field_Query_session_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) { + var err error + args := map[string]interface{}{} + var arg0 *model.SessionQueryInput + if tmp, ok := rawArgs["params"]; ok { + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params")) + arg0, err = ec.unmarshalOSessionQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐSessionQueryInput(ctx, tmp) + if err != nil { + return nil, err + } + } + args["params"] = arg0 return args, nil } @@ -3566,7 +3629,7 @@ func (ec *executionContext) _Query_session(ctx context.Context, field graphql.Co fc.Args = args resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { ctx = rctx // use context from middleware stack in children - return ec.resolvers.Query().Session(rctx, args["roles"].([]string)) + return ec.resolvers.Query().Session(rctx, args["params"].(*model.SessionQueryInput)) }) if err != nil { ec.Error(ctx, err) @@ -3583,6 +3646,48 @@ func (ec *executionContext) _Query_session(ctx context.Context, field graphql.Co return ec.marshalNAuthResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐAuthResponse(ctx, field.Selections, res) } +func (ec *executionContext) _Query_is_valid_jwt(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "Query", + Field: field, + Args: nil, + IsMethod: true, + IsResolver: true, + } + + ctx = graphql.WithFieldContext(ctx, fc) + rawArgs := field.ArgumentMap(ec.Variables) + args, err := ec.field_Query_is_valid_jwt_args(ctx, rawArgs) + if err != nil { + ec.Error(ctx, err) + return graphql.Null + } + fc.Args = args + resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (interface{}, error) { + ctx = rctx // use context from middleware stack in children + return ec.resolvers.Query().IsValidJwt(rctx, args["params"].(*model.IsValidJWTQueryInput)) + }) + 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.ValidJWTResponse) + fc.Result = res + return ec.marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx, field.Selections, res) +} + func (ec *executionContext) _Query_profile(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -4423,6 +4528,76 @@ func (ec *executionContext) _User_updated_at(ctx context.Context, field graphql. return ec.marshalOInt642ᚖint64(ctx, field.Selections, res) } +func (ec *executionContext) _ValidJWTResponse_valid(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ValidJWTResponse", + 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.Valid, 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) _ValidJWTResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.ValidJWTResponse) (ret graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = graphql.Null + } + }() + fc := &graphql.FieldContext{ + Object: "ValidJWTResponse", + 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.Message, 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.(string) + fc.Result = res + return ec.marshalNString2string(ctx, field.Selections, res) +} + func (ec *executionContext) _VerificationRequest_id(ctx context.Context, field graphql.CollectedField, obj *model.VerificationRequest) (ret graphql.Marshaler) { defer func() { if r := recover(); r != nil { @@ -5864,6 +6039,37 @@ func (ec *executionContext) unmarshalInputForgotPasswordInput(ctx context.Contex return it, nil } +func (ec *executionContext) unmarshalInputIsValidJWTQueryInput(ctx context.Context, obj interface{}) (model.IsValidJWTQueryInput, error) { + var it model.IsValidJWTQueryInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + case "jwt": + var err error + + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("jwt")) + it.Jwt, 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) unmarshalInputLoginInput(ctx context.Context, obj interface{}) (model.LoginInput, error) { var it model.LoginInput asMap := map[string]interface{}{} @@ -6004,6 +6210,29 @@ func (ec *executionContext) unmarshalInputResetPasswordInput(ctx context.Context return it, nil } +func (ec *executionContext) unmarshalInputSessionQueryInput(ctx context.Context, obj interface{}) (model.SessionQueryInput, error) { + var it model.SessionQueryInput + asMap := map[string]interface{}{} + for k, v := range obj.(map[string]interface{}) { + asMap[k] = v + } + + for k, v := range asMap { + switch k { + 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) unmarshalInputSignUpInput(ctx context.Context, obj interface{}) (model.SignUpInput, error) { var it model.SignUpInput asMap := map[string]interface{}{} @@ -6971,6 +7200,20 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr } return res }) + case "is_valid_jwt": + field := field + out.Concurrently(i, func() (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._Query_is_valid_jwt(ctx, field) + if res == graphql.Null { + atomic.AddUint32(&invalids, 1) + } + return res + }) case "profile": field := field out.Concurrently(i, func() (res graphql.Marshaler) { @@ -7154,6 +7397,38 @@ func (ec *executionContext) _User(ctx context.Context, sel ast.SelectionSet, obj return out } +var validJWTResponseImplementors = []string{"ValidJWTResponse"} + +func (ec *executionContext) _ValidJWTResponse(ctx context.Context, sel ast.SelectionSet, obj *model.ValidJWTResponse) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, validJWTResponseImplementors) + + out := graphql.NewFieldSet(fields) + var invalids uint32 + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("ValidJWTResponse") + case "valid": + out.Values[i] = ec._ValidJWTResponse_valid(ctx, field, obj) + if out.Values[i] == graphql.Null { + invalids++ + } + case "message": + out.Values[i] = ec._ValidJWTResponse_message(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"} func (ec *executionContext) _VerificationRequest(ctx context.Context, sel ast.SelectionSet, obj *model.VerificationRequest) graphql.Marshaler { @@ -7698,6 +7973,20 @@ func (ec *executionContext) marshalNUser2ᚖgithubᚗcomᚋauthorizerdevᚋautho return ec._User(ctx, sel, v) } +func (ec *executionContext) marshalNValidJWTResponse2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v model.ValidJWTResponse) graphql.Marshaler { + return ec._ValidJWTResponse(ctx, sel, &v) +} + +func (ec *executionContext) marshalNValidJWTResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐValidJWTResponse(ctx context.Context, sel ast.SelectionSet, v *model.ValidJWTResponse) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + ec.Errorf(ctx, "must not be null") + } + return graphql.Null + } + return ec._ValidJWTResponse(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 { ret := make(graphql.Array, len(v)) var wg sync.WaitGroup @@ -8053,6 +8342,22 @@ func (ec *executionContext) marshalOInt642ᚖint64(ctx context.Context, sel ast. return graphql.MarshalInt64(*v) } +func (ec *executionContext) unmarshalOIsValidJWTQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐIsValidJWTQueryInput(ctx context.Context, v interface{}) (*model.IsValidJWTQueryInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputIsValidJWTQueryInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) unmarshalOSessionQueryInput2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐSessionQueryInput(ctx context.Context, v interface{}) (*model.SessionQueryInput, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputSessionQueryInput(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOString2string(ctx context.Context, v interface{}) (string, error) { res, err := graphql.UnmarshalString(v) return res, graphql.ErrorOnPath(ctx, err) diff --git a/server/graph/model/models_gen.go b/server/graph/model/models_gen.go index d0a3afb..f966d57 100644 --- a/server/graph/model/models_gen.go +++ b/server/graph/model/models_gen.go @@ -63,6 +63,11 @@ type ForgotPasswordInput struct { Email string `json:"email"` } +type IsValidJWTQueryInput struct { + Jwt string `json:"jwt"` + Roles []string `json:"roles"` +} + type LoginInput struct { Email string `json:"email"` Password string `json:"password"` @@ -99,6 +104,10 @@ type Response struct { Message string `json:"message"` } +type SessionQueryInput struct { + Roles []string `json:"roles"` +} + type SignUpInput struct { Email string `json:"email"` GivenName *string `json:"given_name"` @@ -197,6 +206,11 @@ type User struct { UpdatedAt *int64 `json:"updated_at"` } +type ValidJWTResponse struct { + Valid bool `json:"valid"` + Message string `json:"message"` +} + type VerificationRequest struct { ID string `json:"id"` Identifier *string `json:"identifier"` diff --git a/server/graph/schema.graphqls b/server/graph/schema.graphqls index cc2c75b..45a04dc 100644 --- a/server/graph/schema.graphqls +++ b/server/graph/schema.graphqls @@ -62,6 +62,11 @@ type Response { message: String! } +type ValidJWTResponse { + valid: Boolean! + message: String! +} + type Env { ADMIN_SECRET: String SMTP_HOST: String @@ -215,6 +220,15 @@ input MagicLinkLoginInput { roles: [String!] } +input SessionQueryInput { + roles: [String!] +} + +input IsValidJWTQueryInput { + jwt: String! + roles: [String!] +} + type Mutation { signup(params: SignUpInput!): AuthResponse! login(params: LoginInput!): AuthResponse! @@ -236,7 +250,8 @@ type Mutation { type Query { meta: Meta! - session(roles: [String!]): AuthResponse! + session(params: SessionQueryInput): AuthResponse! + is_valid_jwt(params: IsValidJWTQueryInput): ValidJWTResponse! profile: User! # admin only apis _users: [User!]! diff --git a/server/graph/schema.resolvers.go b/server/graph/schema.resolvers.go index eafdc57..b06e51d 100644 --- a/server/graph/schema.resolvers.go +++ b/server/graph/schema.resolvers.go @@ -75,8 +75,12 @@ func (r *queryResolver) Meta(ctx context.Context) (*model.Meta, error) { return resolvers.MetaResolver(ctx) } -func (r *queryResolver) Session(ctx context.Context, roles []string) (*model.AuthResponse, error) { - return resolvers.SessionResolver(ctx, roles) +func (r *queryResolver) Session(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) { + return resolvers.SessionResolver(ctx, params) +} + +func (r *queryResolver) IsValidJwt(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) { + return resolvers.IsValidJwtResolver(ctx, params) } func (r *queryResolver) Profile(ctx context.Context) (*model.User, error) { diff --git a/server/resolvers/is_valid_jwt.go b/server/resolvers/is_valid_jwt.go new file mode 100644 index 0000000..8553ab2 --- /dev/null +++ b/server/resolvers/is_valid_jwt.go @@ -0,0 +1,39 @@ +package resolvers + +import ( + "context" + "fmt" + + "github.com/authorizerdev/authorizer/server/constants" + "github.com/authorizerdev/authorizer/server/envstore" + "github.com/authorizerdev/authorizer/server/graph/model" + tokenHelper "github.com/authorizerdev/authorizer/server/token" + "github.com/authorizerdev/authorizer/server/utils" +) + +// IsValidJwtResolver resolver to return if given jwt is valid +func IsValidJwtResolver(ctx context.Context, params *model.IsValidJWTQueryInput) (*model.ValidJWTResponse, error) { + claims, err := tokenHelper.VerifyJWTToken(params.Jwt) + if err != nil { + return nil, err + } + + claimRoleInterface := claims[envstore.EnvInMemoryStoreObj.GetStringStoreEnvVariable(constants.EnvKeyJwtRoleClaim)].([]interface{}) + claimRoles := []string{} + for _, v := range claimRoleInterface { + claimRoles = append(claimRoles, v.(string)) + } + + if params != nil && params.Roles != nil && len(params.Roles) > 0 { + for _, v := range params.Roles { + if !utils.StringSliceContains(claimRoles, v) { + return nil, fmt.Errorf(`unauthorized`) + } + } + } + + return &model.ValidJWTResponse{ + Valid: true, + Message: "Valid JWT", + }, nil +} diff --git a/server/resolvers/session.go b/server/resolvers/session.go index 8f13a34..175fcf9 100644 --- a/server/resolvers/session.go +++ b/server/resolvers/session.go @@ -13,7 +13,7 @@ import ( ) // SessionResolver is a resolver for session query -func SessionResolver(ctx context.Context, roles []string) (*model.AuthResponse, error) { +func SessionResolver(ctx context.Context, params *model.SessionQueryInput) (*model.AuthResponse, error) { var res *model.AuthResponse gc, err := utils.GinContextFromContext(ctx) @@ -65,8 +65,8 @@ func SessionResolver(ctx context.Context, roles []string) (*model.AuthResponse, claimRoles = append(claimRoles, v.(string)) } - if len(roles) > 0 { - for _, v := range roles { + if params != nil && params.Roles != nil && len(params.Roles) > 0 { + for _, v := range params.Roles { if !utils.StringSliceContains(claimRoles, v) { return res, fmt.Errorf(`unauthorized`) } diff --git a/server/test/is_valid_jwt_test.go b/server/test/is_valid_jwt_test.go new file mode 100644 index 0000000..2c47693 --- /dev/null +++ b/server/test/is_valid_jwt_test.go @@ -0,0 +1,39 @@ +package test + +import ( + "context" + "testing" + + "github.com/authorizerdev/authorizer/server/db/models" + "github.com/authorizerdev/authorizer/server/graph/model" + "github.com/authorizerdev/authorizer/server/resolvers" + "github.com/authorizerdev/authorizer/server/token" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +func isValidJWTTests(t *testing.T, s TestSetup) { + t.Helper() + ctx := context.Background() + expiredToken := "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhbGxvd2VkX3JvbGVzIjpbIiJdLCJiaXJ0aGRhdGUiOm51bGwsImNyZWF0ZWRfYXQiOjAsImVtYWlsIjoiam9obi5kb2VAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJleHAiOjE2NDI5NjEwMTEsImV4dHJhIjp7IngtZXh0cmEtaWQiOiJkMmNhMjQwNy05MzZmLTQwYzQtOTQ2NS05Y2M5MWYxZTJhNDQifSwiZmFtaWx5X25hbWUiOm51bGwsImdlbmRlciI6bnVsbCwiZ2l2ZW5fbmFtZSI6bnVsbCwiaWF0IjoxNjQyOTYwOTgxLCJpZCI6ImQyY2EyNDA3LTkzNmYtNDBjNC05NDY1LTljYzkxZjFlMmE0NCIsIm1pZGRsZV9uYW1lIjpudWxsLCJuaWNrbmFtZSI6bnVsbCwicGhvbmVfbnVtYmVyIjpudWxsLCJwaG9uZV9udW1iZXJfdmVyaWZpZWQiOmZhbHNlLCJwaWN0dXJlIjpudWxsLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqb2huLmRvZUBnbWFpbC5jb20iLCJyb2xlIjpbXSwic2lnbnVwX21ldGhvZHMiOiIiLCJ0b2tlbl90eXBlIjoiYWNjZXNzX3Rva2VuIiwidXBkYXRlZF9hdCI6MH0.FrdyeOC5e8uU1SowGj0omFJuwRnh4BrEk89S_fbEkzs" + + t.Run(`should fail for invalid jwt`, func(t *testing.T) { + _, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{ + Jwt: expiredToken, + }) + assert.NotNil(t, err) + }) + + t.Run(`should pass with valid jwt`, func(t *testing.T) { + authToken, err := token.CreateAuthToken(models.User{ + ID: uuid.New().String(), + Email: "john.doe@gmail.com", + }, []string{}) + assert.Nil(t, err) + res, err := resolvers.IsValidJwtResolver(ctx, &model.IsValidJWTQueryInput{ + Jwt: authToken.AccessToken.Token, + }) + assert.Nil(t, err) + assert.True(t, res.Valid) + }) +} diff --git a/server/test/resolvers_test.go b/server/test/resolvers_test.go index 029b8c9..8ef351a 100644 --- a/server/test/resolvers_test.go +++ b/server/test/resolvers_test.go @@ -60,6 +60,7 @@ func TestResolvers(t *testing.T) { magicLinkLoginTests(t, s) logoutTests(t, s) metaTests(t, s) + isValidJWTTests(t, s) }) } } diff --git a/server/test/session_test.go b/server/test/session_test.go index ed001f6..d28f585 100644 --- a/server/test/session_test.go +++ b/server/test/session_test.go @@ -27,7 +27,7 @@ func sessionTests(t *testing.T, s TestSetup) { ConfirmPassword: s.TestInfo.Password, }) - _, err := resolvers.SessionResolver(ctx, []string{}) + _, err := resolvers.SessionResolver(ctx, &model.SessionQueryInput{}) assert.NotNil(t, err, "unauthorized") verificationRequest, err := db.Provider.GetVerificationRequestByEmail(email, constants.VerificationTypeBasicAuthSignup) @@ -50,13 +50,9 @@ func sessionTests(t *testing.T, s TestSetup) { req.Header.Set("Cookie", cookie) - _, err = resolvers.SessionResolver(ctx, []string{}) + _, err = resolvers.SessionResolver(ctx, &model.SessionQueryInput{}) assert.Nil(t, err) - // newToken := *sessionRes.AccessToken - - // assert.NotEqual(t, token, newToken, "tokens should not be equal") - cleanData(email) }) }