Compare commits
7 Commits
0.14.0-bet
...
0.14.0-bet
Author | SHA1 | Date | |
---|---|---|---|
![]() |
9eca697a91 | ||
![]() |
7136ee924d | ||
![]() |
fd9eb7c733 | ||
![]() |
917eaeb2ed | ||
![]() |
3bb90acc9e | ||
![]() |
a69b8e290c | ||
![]() |
674eeeea4e |
14
app/package-lock.json
generated
14
app/package-lock.json
generated
@@ -9,7 +9,7 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-react": "0.9.0-beta.0",
|
||||
"@authorizerdev/authorizer-react": "0.9.0-beta.2",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"esbuild": "^0.12.17",
|
||||
@@ -35,9 +35,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@authorizerdev/authorizer-react": {
|
||||
"version": "0.9.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.0.tgz",
|
||||
"integrity": "sha512-+I0JGobxuTDwKPAqwkJuczXnQ+ooNnz9IrfQPl8ZHnKoZsRqSSPCOLO6o0BLBu65h6pWZdLEEFN8CjRnu1+Zuw==",
|
||||
"version": "0.9.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.2.tgz",
|
||||
"integrity": "sha512-clngw7MdFzvnns9rgrg9fHRH4p3K+HGGMju6qhdjDF+4vPruSu6HwBi1hRvVxLi1q7CZ25CEE7CfA7Vfq7H3Bw==",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-js": "^0.4.0-beta.0",
|
||||
"final-form": "^4.20.2",
|
||||
@@ -837,9 +837,9 @@
|
||||
}
|
||||
},
|
||||
"@authorizerdev/authorizer-react": {
|
||||
"version": "0.9.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.0.tgz",
|
||||
"integrity": "sha512-+I0JGobxuTDwKPAqwkJuczXnQ+ooNnz9IrfQPl8ZHnKoZsRqSSPCOLO6o0BLBu65h6pWZdLEEFN8CjRnu1+Zuw==",
|
||||
"version": "0.9.0-beta.2",
|
||||
"resolved": "https://registry.npmjs.org/@authorizerdev/authorizer-react/-/authorizer-react-0.9.0-beta.2.tgz",
|
||||
"integrity": "sha512-clngw7MdFzvnns9rgrg9fHRH4p3K+HGGMju6qhdjDF+4vPruSu6HwBi1hRvVxLi1q7CZ25CEE7CfA7Vfq7H3Bw==",
|
||||
"requires": {
|
||||
"@authorizerdev/authorizer-js": "^0.4.0-beta.0",
|
||||
"final-form": "^4.20.2",
|
||||
|
@@ -11,7 +11,7 @@
|
||||
"author": "Lakhan Samani",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@authorizerdev/authorizer-react": "0.9.0-beta.0",
|
||||
"@authorizerdev/authorizer-react": "0.9.0-beta.2",
|
||||
"@types/react": "^17.0.15",
|
||||
"@types/react-dom": "^17.0.9",
|
||||
"esbuild": "^0.12.17",
|
||||
|
@@ -119,6 +119,7 @@ type ComplexityRoot struct {
|
||||
MagicLinkLogin func(childComplexity int, params model.MagicLinkLoginInput) int
|
||||
ResendVerifyEmail func(childComplexity int, params model.ResendVerifyEmailInput) int
|
||||
ResetPassword func(childComplexity int, params model.ResetPasswordInput) int
|
||||
Revoke func(childComplexity int, params model.OAuthRevokeInput) int
|
||||
Signup func(childComplexity int, params model.SignUpInput) int
|
||||
UpdateEnv func(childComplexity int, params model.UpdateEnvInput) int
|
||||
UpdateProfile func(childComplexity int, params model.UpdateProfileInput) int
|
||||
@@ -200,6 +201,7 @@ type MutationResolver interface {
|
||||
ResendVerifyEmail(ctx context.Context, params model.ResendVerifyEmailInput) (*model.Response, error)
|
||||
ForgotPassword(ctx context.Context, params model.ForgotPasswordInput) (*model.Response, error)
|
||||
ResetPassword(ctx context.Context, params model.ResetPasswordInput) (*model.Response, error)
|
||||
Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error)
|
||||
DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error)
|
||||
UpdateUser(ctx context.Context, params model.UpdateUserInput) (*model.User, error)
|
||||
AdminSignup(ctx context.Context, params model.AdminSignupInput) (*model.Response, error)
|
||||
@@ -713,6 +715,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
|
||||
|
||||
return e.complexity.Mutation.ResetPassword(childComplexity, args["params"].(model.ResetPasswordInput)), true
|
||||
|
||||
case "Mutation.revoke":
|
||||
if e.complexity.Mutation.Revoke == nil {
|
||||
break
|
||||
}
|
||||
|
||||
args, err := ec.field_Mutation_revoke_args(context.TODO(), rawArgs)
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return e.complexity.Mutation.Revoke(childComplexity, args["params"].(model.OAuthRevokeInput)), true
|
||||
|
||||
case "Mutation.signup":
|
||||
if e.complexity.Mutation.Signup == nil {
|
||||
break
|
||||
@@ -1415,6 +1429,10 @@ input PaginatedInput {
|
||||
pagination: PaginationInput
|
||||
}
|
||||
|
||||
input OAuthRevokeInput {
|
||||
refresh_token: String!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
signup(params: SignUpInput!): AuthResponse!
|
||||
login(params: LoginInput!): AuthResponse!
|
||||
@@ -1425,6 +1443,7 @@ type Mutation {
|
||||
resend_verify_email(params: ResendVerifyEmailInput!): Response!
|
||||
forgot_password(params: ForgotPasswordInput!): Response!
|
||||
reset_password(params: ResetPasswordInput!): Response!
|
||||
revoke(params: OAuthRevokeInput!): Response!
|
||||
# admin only apis
|
||||
_delete_user(params: DeleteUserInput!): Response!
|
||||
_update_user(params: UpdateUserInput!): User!
|
||||
@@ -1602,6 +1621,21 @@ func (ec *executionContext) field_Mutation_reset_password_args(ctx context.Conte
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_revoke_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
var arg0 model.OAuthRevokeInput
|
||||
if tmp, ok := rawArgs["params"]; ok {
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("params"))
|
||||
arg0, err = ec.unmarshalNOAuthRevokeInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐOAuthRevokeInput(ctx, tmp)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
args["params"] = arg0
|
||||
return args, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) field_Mutation_signup_args(ctx context.Context, rawArgs map[string]interface{}) (map[string]interface{}, error) {
|
||||
var err error
|
||||
args := map[string]interface{}{}
|
||||
@@ -3860,6 +3894,48 @@ func (ec *executionContext) _Mutation_reset_password(ctx context.Context, field
|
||||
return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation_revoke(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: "Mutation",
|
||||
Field: field,
|
||||
Args: nil,
|
||||
IsMethod: true,
|
||||
IsResolver: true,
|
||||
}
|
||||
|
||||
ctx = graphql.WithFieldContext(ctx, fc)
|
||||
rawArgs := field.ArgumentMap(ec.Variables)
|
||||
args, err := ec.field_Mutation_revoke_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.Mutation().Revoke(rctx, args["params"].(model.OAuthRevokeInput))
|
||||
})
|
||||
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.Response)
|
||||
fc.Result = res
|
||||
return ec.marshalNResponse2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐResponse(ctx, field.Selections, res)
|
||||
}
|
||||
|
||||
func (ec *executionContext) _Mutation__delete_user(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
@@ -6939,6 +7015,29 @@ func (ec *executionContext) unmarshalInputMagicLinkLoginInput(ctx context.Contex
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputOAuthRevokeInput(ctx context.Context, obj interface{}) (model.OAuthRevokeInput, error) {
|
||||
var it model.OAuthRevokeInput
|
||||
asMap := map[string]interface{}{}
|
||||
for k, v := range obj.(map[string]interface{}) {
|
||||
asMap[k] = v
|
||||
}
|
||||
|
||||
for k, v := range asMap {
|
||||
switch k {
|
||||
case "refresh_token":
|
||||
var err error
|
||||
|
||||
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("refresh_token"))
|
||||
it.RefreshToken, err = ec.unmarshalNString2string(ctx, v)
|
||||
if err != nil {
|
||||
return it, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return it, nil
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalInputPaginatedInput(ctx context.Context, obj interface{}) (model.PaginatedInput, error) {
|
||||
var it model.PaginatedInput
|
||||
asMap := map[string]interface{}{}
|
||||
@@ -8039,6 +8138,11 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "revoke":
|
||||
out.Values[i] = ec._Mutation_revoke(ctx, field)
|
||||
if out.Values[i] == graphql.Null {
|
||||
invalids++
|
||||
}
|
||||
case "_delete_user":
|
||||
out.Values[i] = ec._Mutation__delete_user(ctx, field)
|
||||
if out.Values[i] == graphql.Null {
|
||||
@@ -8822,6 +8926,11 @@ func (ec *executionContext) marshalNMeta2ᚖgithubᚗcomᚋauthorizerdevᚋautho
|
||||
return ec._Meta(ctx, sel, v)
|
||||
}
|
||||
|
||||
func (ec *executionContext) unmarshalNOAuthRevokeInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐOAuthRevokeInput(ctx context.Context, v interface{}) (model.OAuthRevokeInput, error) {
|
||||
res, err := ec.unmarshalInputOAuthRevokeInput(ctx, v)
|
||||
return res, graphql.ErrorOnPath(ctx, err)
|
||||
}
|
||||
|
||||
func (ec *executionContext) marshalNPagination2ᚖgithubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐPagination(ctx context.Context, sel ast.SelectionSet, v *model.Pagination) graphql.Marshaler {
|
||||
if v == nil {
|
||||
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
|
||||
|
@@ -100,6 +100,10 @@ type Meta struct {
|
||||
IsMagicLinkLoginEnabled bool `json:"is_magic_link_login_enabled"`
|
||||
}
|
||||
|
||||
type OAuthRevokeInput struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
}
|
||||
|
||||
type PaginatedInput struct {
|
||||
Pagination *PaginationInput `json:"pagination"`
|
||||
}
|
||||
|
@@ -267,6 +267,10 @@ input PaginatedInput {
|
||||
pagination: PaginationInput
|
||||
}
|
||||
|
||||
input OAuthRevokeInput {
|
||||
refresh_token: String!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
signup(params: SignUpInput!): AuthResponse!
|
||||
login(params: LoginInput!): AuthResponse!
|
||||
@@ -277,6 +281,7 @@ type Mutation {
|
||||
resend_verify_email(params: ResendVerifyEmailInput!): Response!
|
||||
forgot_password(params: ForgotPasswordInput!): Response!
|
||||
reset_password(params: ResetPasswordInput!): Response!
|
||||
revoke(params: OAuthRevokeInput!): Response!
|
||||
# admin only apis
|
||||
_delete_user(params: DeleteUserInput!): Response!
|
||||
_update_user(params: UpdateUserInput!): User!
|
||||
|
@@ -47,6 +47,10 @@ func (r *mutationResolver) ResetPassword(ctx context.Context, params model.Reset
|
||||
return resolvers.ResetPasswordResolver(ctx, params)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) Revoke(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) {
|
||||
return resolvers.RevokeResolver(ctx, params)
|
||||
}
|
||||
|
||||
func (r *mutationResolver) DeleteUser(ctx context.Context, params model.DeleteUserInput) (*model.Response, error) {
|
||||
return resolvers.DeleteUserResolver(ctx, params)
|
||||
}
|
||||
@@ -105,5 +109,7 @@ func (r *Resolver) Mutation() generated.MutationResolver { return &mutationResol
|
||||
// Query returns generated.QueryResolver implementation.
|
||||
func (r *Resolver) Query() generated.QueryResolver { return &queryResolver{r} }
|
||||
|
||||
type mutationResolver struct{ *Resolver }
|
||||
type queryResolver struct{ *Resolver }
|
||||
type (
|
||||
mutationResolver struct{ *Resolver }
|
||||
queryResolver struct{ *Resolver }
|
||||
)
|
||||
|
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Handler to logout user
|
||||
func LogoutHandler() gin.HandlerFunc {
|
||||
return func(gc *gin.Context) {
|
||||
// get fingerprint hash
|
||||
|
@@ -37,7 +37,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||
}
|
||||
sessionstore.GetState(state)
|
||||
// contains random token, redirect url, role
|
||||
sessionSplit := strings.Split(state, "@")
|
||||
sessionSplit := strings.Split(state, "___")
|
||||
|
||||
if len(sessionSplit) < 3 {
|
||||
c.JSON(400, gin.H{"error": "invalid redirect url"})
|
||||
@@ -158,7 +158,7 @@ func OAuthCallbackHandler() gin.HandlerFunc {
|
||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||
|
||||
if authToken.RefreshToken != nil {
|
||||
params = params + `&refresh_token=${refresh_token}`
|
||||
params = params + `&refresh_token=` + authToken.RefreshToken.Token
|
||||
sessionstore.SetState(authToken.AccessToken.Token, authToken.FingerPrint+"@"+user.ID)
|
||||
}
|
||||
|
||||
|
@@ -58,7 +58,7 @@ func OAuthLoginHandler() gin.HandlerFunc {
|
||||
roles = strings.Join(envstore.EnvStoreObj.GetSliceStoreEnvVariable(constants.EnvKeyDefaultRoles), ",")
|
||||
}
|
||||
|
||||
oauthStateString := state + "@" + redirectURI + "@" + roles + "@" + strings.Join(scope, ",")
|
||||
oauthStateString := state + "___" + redirectURI + "___" + roles + "___" + strings.Join(scope, ",")
|
||||
|
||||
provider := c.Param("oauth_provider")
|
||||
isProviderConfigured := true
|
||||
|
50
server/handlers/revoke.go
Normal file
50
server/handlers/revoke.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/constants"
|
||||
"github.com/authorizerdev/authorizer/server/envstore"
|
||||
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Revoke handler to revoke refresh token
|
||||
func RevokeHandler() gin.HandlerFunc {
|
||||
return func(gc *gin.Context) {
|
||||
var reqBody map[string]string
|
||||
if err := gc.BindJSON(&reqBody); err != nil {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "error_binding_json",
|
||||
"error_description": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
// get fingerprint hash
|
||||
refreshToken := strings.TrimSpace(reqBody["refresh_token"])
|
||||
clientID := strings.TrimSpace(reqBody["client_id"])
|
||||
|
||||
if clientID == "" {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "client_id_required",
|
||||
"error_description": "The client id is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if clientID != envstore.EnvStoreObj.GetStringStoreEnvVariable(constants.EnvKeyClientID) {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_client_id",
|
||||
"error_description": "The client id is invalid",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
sessionstore.RemoveState(refreshToken)
|
||||
|
||||
gc.JSON(http.StatusOK, gin.H{
|
||||
"message": "Token revoked successfully",
|
||||
})
|
||||
}
|
||||
}
|
@@ -15,6 +15,8 @@ import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// TokenHandler to handle /oauth/token requests
|
||||
// grant type required
|
||||
func TokenHandler() gin.HandlerFunc {
|
||||
return func(gc *gin.Context) {
|
||||
var reqBody map[string]string
|
||||
@@ -29,6 +31,22 @@ func TokenHandler() gin.HandlerFunc {
|
||||
codeVerifier := strings.TrimSpace(reqBody["code_verifier"])
|
||||
code := strings.TrimSpace(reqBody["code"])
|
||||
clientID := strings.TrimSpace(reqBody["client_id"])
|
||||
grantType := strings.TrimSpace(reqBody["grant_type"])
|
||||
refreshToken := strings.TrimSpace(reqBody["refresh_token"])
|
||||
|
||||
if grantType == "" {
|
||||
grantType = "authorization_code"
|
||||
}
|
||||
|
||||
isRefreshTokenGrant := grantType == "refresh_token"
|
||||
isAuthorizationCodeGrant := grantType == "authorization_code"
|
||||
|
||||
if !isRefreshTokenGrant && !isAuthorizationCodeGrant {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_grant_type",
|
||||
"error_description": "grant_type is invalid",
|
||||
})
|
||||
}
|
||||
|
||||
if clientID == "" {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
@@ -46,58 +64,89 @@ func TokenHandler() gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
|
||||
if codeVerifier == "" {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_code_verifier",
|
||||
"error_description": "The code verifier is required",
|
||||
})
|
||||
return
|
||||
var userID string
|
||||
var roles, scope []string
|
||||
if isAuthorizationCodeGrant {
|
||||
|
||||
if codeVerifier == "" {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_code_verifier",
|
||||
"error_description": "The code verifier is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if code == "" {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_code",
|
||||
"error_description": "The code is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(codeVerifier))
|
||||
encryptedCode := strings.ReplaceAll(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "+", "-")
|
||||
encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_")
|
||||
encryptedCode = strings.ReplaceAll(encryptedCode, "=", "")
|
||||
sessionData := sessionstore.GetState(encryptedCode)
|
||||
if sessionData == "" {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_code_verifier",
|
||||
"error_description": "The code verifier is invalid",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// split session data
|
||||
// it contains code@sessiontoken
|
||||
sessionDataSplit := strings.Split(sessionData, "@")
|
||||
|
||||
if sessionDataSplit[0] != code {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_code_verifier",
|
||||
"error_description": "The code verifier is invalid",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// rollover the session for security
|
||||
sessionstore.RemoveState(sessionDataSplit[1])
|
||||
// validate session
|
||||
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
|
||||
if err != nil {
|
||||
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"error_description": "Invalid session data",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID = claims.Subject
|
||||
roles = claims.Roles
|
||||
scope = claims.Scope
|
||||
} else {
|
||||
// validate refresh token
|
||||
if refreshToken == "" {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_refresh_token",
|
||||
"error_description": "The refresh token is invalid",
|
||||
})
|
||||
}
|
||||
|
||||
claims, err := token.ValidateRefreshToken(gc, refreshToken)
|
||||
if err != nil {
|
||||
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"error_description": err.Error(),
|
||||
})
|
||||
}
|
||||
userID = claims["sub"].(string)
|
||||
roles = claims["roles"].([]string)
|
||||
scope = claims["scope"].([]string)
|
||||
// remove older refresh token and rotate it for security
|
||||
sessionstore.RemoveState(refreshToken)
|
||||
}
|
||||
|
||||
if code == "" {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_code",
|
||||
"error_description": "The code is required",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
hash := sha256.New()
|
||||
hash.Write([]byte(codeVerifier))
|
||||
encryptedCode := strings.ReplaceAll(base64.URLEncoding.EncodeToString(hash.Sum(nil)), "+", "-")
|
||||
encryptedCode = strings.ReplaceAll(encryptedCode, "/", "_")
|
||||
encryptedCode = strings.ReplaceAll(encryptedCode, "=", "")
|
||||
sessionData := sessionstore.GetState(encryptedCode)
|
||||
if sessionData == "" {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_code_verifier",
|
||||
"error_description": "The code verifier is invalid",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// split session data
|
||||
// it contains code@sessiontoken
|
||||
sessionDataSplit := strings.Split(sessionData, "@")
|
||||
|
||||
if sessionDataSplit[0] != code {
|
||||
gc.JSON(http.StatusBadRequest, gin.H{
|
||||
"error": "invalid_code_verifier",
|
||||
"error_description": "The code verifier is invalid",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// validate session
|
||||
claims, err := token.ValidateBrowserSession(gc, sessionDataSplit[1])
|
||||
if err != nil {
|
||||
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
"error_description": "Invalid session data",
|
||||
})
|
||||
return
|
||||
}
|
||||
userID := claims.Subject
|
||||
user, err := db.Provider.GetUserByID(userID)
|
||||
if err != nil {
|
||||
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||
@@ -106,9 +155,8 @@ func TokenHandler() gin.HandlerFunc {
|
||||
})
|
||||
return
|
||||
}
|
||||
// rollover the session for security
|
||||
sessionstore.RemoveState(sessionDataSplit[1])
|
||||
authToken, err := token.CreateAuthToken(gc, user, claims.Roles, claims.Scope)
|
||||
|
||||
authToken, err := token.CreateAuthToken(gc, user, roles, scope)
|
||||
if err != nil {
|
||||
gc.JSON(http.StatusUnauthorized, gin.H{
|
||||
"error": "unauthorized",
|
||||
@@ -124,7 +172,8 @@ func TokenHandler() gin.HandlerFunc {
|
||||
res := map[string]interface{}{
|
||||
"access_token": authToken.AccessToken.Token,
|
||||
"id_token": authToken.IDToken.Token,
|
||||
"scope": claims.Scope,
|
||||
"scope": scope,
|
||||
"roles": roles,
|
||||
"expires_in": expiresIn,
|
||||
}
|
||||
|
||||
|
16
server/resolvers/revoke.go
Normal file
16
server/resolvers/revoke.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package resolvers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/authorizerdev/authorizer/server/graph/model"
|
||||
"github.com/authorizerdev/authorizer/server/sessionstore"
|
||||
)
|
||||
|
||||
// RevokeResolver resolver to revoke refresh token
|
||||
func RevokeResolver(ctx context.Context, params model.OAuthRevokeInput) (*model.Response, error) {
|
||||
sessionstore.RemoveState(params.RefreshToken)
|
||||
return &model.Response{
|
||||
Message: "Token revoked",
|
||||
}, nil
|
||||
}
|
@@ -27,6 +27,7 @@ func InitRouter() *gin.Engine {
|
||||
router.GET("/userinfo", handlers.UserInfoHandler())
|
||||
router.GET("/logout", handlers.LogoutHandler())
|
||||
router.POST("/oauth/token", handlers.TokenHandler())
|
||||
router.POST("/oauth/revoke", handlers.RevokeHandler())
|
||||
|
||||
router.LoadHTMLGlob("templates/*")
|
||||
// login page app related routes.
|
||||
|
@@ -91,7 +91,7 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (
|
||||
}
|
||||
|
||||
if utils.StringSliceContains(scope, "offline_access") {
|
||||
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, hostname, nonce)
|
||||
refreshToken, refreshTokenExpiresAt, err := CreateRefreshToken(user, roles, scope, hostname, nonce)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -103,7 +103,7 @@ func CreateAuthToken(gc *gin.Context, user models.User, roles, scope []string) (
|
||||
}
|
||||
|
||||
// CreateRefreshToken util to create JWT token
|
||||
func CreateRefreshToken(user models.User, roles []string, hostname, nonce string) (string, int64, error) {
|
||||
func CreateRefreshToken(user models.User, roles, scopes []string, hostname, nonce string) (string, int64, error) {
|
||||
// expires in 1 year
|
||||
expiryBound := time.Hour * 8760
|
||||
expiresAt := time.Now().Add(expiryBound).Unix()
|
||||
@@ -115,6 +115,7 @@ func CreateRefreshToken(user models.User, roles []string, hostname, nonce string
|
||||
"iat": time.Now().Unix(),
|
||||
"token_type": constants.TokenTypeRefreshToken,
|
||||
"roles": roles,
|
||||
"scope": scopes,
|
||||
"nonce": nonce,
|
||||
}
|
||||
|
||||
@@ -198,6 +199,36 @@ func ValidateAccessToken(gc *gin.Context, accessToken string) (map[string]interf
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// Function to validate refreshToken
|
||||
func ValidateRefreshToken(gc *gin.Context, refreshToken string) (map[string]interface{}, error) {
|
||||
var res map[string]interface{}
|
||||
|
||||
if refreshToken == "" {
|
||||
return res, fmt.Errorf(`unauthorized`)
|
||||
}
|
||||
|
||||
savedSession := sessionstore.GetState(refreshToken)
|
||||
if savedSession == "" {
|
||||
return res, fmt.Errorf(`unauthorized`)
|
||||
}
|
||||
|
||||
savedSessionSplit := strings.Split(savedSession, "@")
|
||||
nonce := savedSessionSplit[0]
|
||||
userID := savedSessionSplit[1]
|
||||
|
||||
hostname := utils.GetHost(gc)
|
||||
res, err := ParseJWTToken(refreshToken, hostname, nonce, userID)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
if res["token_type"] != constants.TokenTypeRefreshToken {
|
||||
return res, fmt.Errorf(`unauthorized: invalid token type`)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func ValidateBrowserSession(gc *gin.Context, encryptedSession string) (*SessionData, error) {
|
||||
if encryptedSession == "" {
|
||||
return nil, fmt.Errorf(`unauthorized`)
|
||||
|
Reference in New Issue
Block a user