feat: add admin session api

This commit is contained in:
Lakhan Samani 2021-12-31 14:28:00 +05:30
parent e35d0cbcd6
commit 217410e9a4
10 changed files with 84 additions and 110 deletions

View File

@ -3,6 +3,7 @@ package test
import ( import (
"testing" "testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/resolvers" "github.com/authorizerdev/authorizer/server/resolvers"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -18,7 +19,7 @@ func aminLoginTests(s TestSetup, t *testing.T) {
assert.NotNil(t, err) assert.NotNil(t, err)
res, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{ res, err := resolvers.AdminLoginResolver(ctx, model.AdminLoginInput{
AdminSecret: "admin", AdminSecret: constants.EnvData.ADMIN_SECRET,
}) })
assert.Nil(t, err) assert.Nil(t, err)

View File

@ -0,0 +1,28 @@
package test
import (
"log"
"testing"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/resolvers"
"github.com/authorizerdev/authorizer/server/utils"
"github.com/stretchr/testify/assert"
)
func aminSessionTests(s TestSetup, t *testing.T) {
t.Run(`should get admin session`, func(t *testing.T) {
req, ctx := createContext(s)
_, err := resolvers.AdminSession(ctx)
log.Println("error:", err)
assert.NotNil(t, err)
h, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
assert.Nil(t, err)
req.Header.Add("Authorization", "Bearer "+h)
res, err := resolvers.AdminSession(ctx)
assert.Nil(t, err)
assert.Greater(t, len(res.AccessToken), 0)
})
}

View File

@ -43,6 +43,7 @@ func TestResolvers(t *testing.T) {
deleteUserTest(s, t) deleteUserTest(s, t)
updateUserTest(s, t) updateUserTest(s, t)
aminLoginTests(s, t) aminLoginTests(s, t)
aminSessionTests(s, t)
}) })
} }
} }

View File

@ -45,7 +45,6 @@ type DirectiveRoot struct {
type ComplexityRoot struct { type ComplexityRoot struct {
AdminLoginResponse struct { AdminLoginResponse struct {
AccessToken func(childComplexity int) int AccessToken func(childComplexity int) int
ExpiresAt func(childComplexity int) int
Message func(childComplexity int) int Message func(childComplexity int) int
} }
@ -175,13 +174,6 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.AdminLoginResponse.AccessToken(childComplexity), true return e.complexity.AdminLoginResponse.AccessToken(childComplexity), true
case "AdminLoginResponse.expires_at":
if e.complexity.AdminLoginResponse.ExpiresAt == nil {
break
}
return e.complexity.AdminLoginResponse.ExpiresAt(childComplexity), true
case "AdminLoginResponse.message": case "AdminLoginResponse.message":
if e.complexity.AdminLoginResponse.Message == nil { if e.complexity.AdminLoginResponse.Message == nil {
break break
@ -772,7 +764,6 @@ type Response {
type AdminLoginResponse { type AdminLoginResponse {
message: String! message: String!
access_token: String! access_token: String!
expires_at: Int64!
} }
input AdminLoginInput { input AdminLoginInput {
@ -1193,41 +1184,6 @@ func (ec *executionContext) _AdminLoginResponse_access_token(ctx context.Context
return ec.marshalNString2string(ctx, field.Selections, res) return ec.marshalNString2string(ctx, field.Selections, res)
} }
func (ec *executionContext) _AdminLoginResponse_expires_at(ctx context.Context, field graphql.CollectedField, obj *model.AdminLoginResponse) (ret graphql.Marshaler) {
defer func() {
if r := recover(); r != nil {
ec.Error(ctx, ec.Recover(ctx, r))
ret = graphql.Null
}
}()
fc := &graphql.FieldContext{
Object: "AdminLoginResponse",
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.ExpiresAt, 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.(int64)
fc.Result = res
return ec.marshalNInt642int64(ctx, field.Selections, res)
}
func (ec *executionContext) _AuthResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) { func (ec *executionContext) _AuthResponse_message(ctx context.Context, field graphql.CollectedField, obj *model.AuthResponse) (ret graphql.Marshaler) {
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
@ -4979,11 +4935,6 @@ func (ec *executionContext) _AdminLoginResponse(ctx context.Context, sel ast.Sel
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
invalids++ invalids++
} }
case "expires_at":
out.Values[i] = ec._AdminLoginResponse_expires_at(ctx, field, obj)
if out.Values[i] == graphql.Null {
invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@ -5754,21 +5705,6 @@ func (ec *executionContext) marshalNID2string(ctx context.Context, sel ast.Selec
return res return res
} }
func (ec *executionContext) unmarshalNInt642int64(ctx context.Context, v interface{}) (int64, error) {
res, err := graphql.UnmarshalInt64(v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNInt642int64(ctx context.Context, sel ast.SelectionSet, v int64) graphql.Marshaler {
res := graphql.MarshalInt64(v)
if res == graphql.Null {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "must not be null")
}
}
return res
}
func (ec *executionContext) unmarshalNLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐLoginInput(ctx context.Context, v interface{}) (model.LoginInput, error) { func (ec *executionContext) unmarshalNLoginInput2githubᚗcomᚋauthorizerdevᚋauthorizerᚋserverᚋgraphᚋmodelᚐLoginInput(ctx context.Context, v interface{}) (model.LoginInput, error) {
res, err := ec.unmarshalInputLoginInput(ctx, v) res, err := ec.unmarshalInputLoginInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err) return res, graphql.ErrorOnPath(ctx, err)

View File

@ -9,7 +9,6 @@ type AdminLoginInput struct {
type AdminLoginResponse struct { type AdminLoginResponse struct {
Message string `json:"message"` Message string `json:"message"`
AccessToken string `json:"access_token"` AccessToken string `json:"access_token"`
ExpiresAt int64 `json:"expires_at"`
} }
type AuthResponse struct { type AuthResponse struct {

View File

@ -65,7 +65,6 @@ type Response {
type AdminLoginResponse { type AdminLoginResponse {
message: String! message: String!
access_token: String! access_token: String!
expires_at: Int64!
} }
input AdminLoginInput { input AdminLoginInput {

View File

@ -5,7 +5,6 @@ package graph
import ( import (
"context" "context"
"fmt"
"github.com/authorizerdev/authorizer/server/graph/generated" "github.com/authorizerdev/authorizer/server/graph/generated"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
@ -81,7 +80,7 @@ func (r *queryResolver) VerificationRequests(ctx context.Context) ([]*model.Veri
} }
func (r *queryResolver) AdminSession(ctx context.Context) (*model.AdminLoginResponse, error) { func (r *queryResolver) AdminSession(ctx context.Context) (*model.AdminLoginResponse, error) {
panic(fmt.Errorf("not implemented")) return resolvers.AdminSession(ctx)
} }
// Mutation returns generated.MutationResolver implementation. // Mutation returns generated.MutationResolver implementation.

View File

@ -3,38 +3,32 @@ package resolvers
import ( import (
"context" "context"
"fmt" "fmt"
"log"
"time"
"github.com/authorizerdev/authorizer/server/constants" "github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/enum"
"github.com/authorizerdev/authorizer/server/graph/model" "github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/session"
"github.com/authorizerdev/authorizer/server/utils" "github.com/authorizerdev/authorizer/server/utils"
) )
func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*model.AdminLoginResponse, error) { func AdminLoginResolver(ctx context.Context, params model.AdminLoginInput) (*model.AdminLoginResponse, error) {
gc, err := utils.GinContextFromContext(ctx) gc, err := utils.GinContextFromContext(ctx)
var res *model.AdminLoginResponse var res *model.AdminLoginResponse
if err != nil { if err != nil {
log.Println("=> error:", err)
return res, err return res, err
} }
if params.AdminSecret != constants.EnvData.ADMIN_SECRET { if params.AdminSecret != constants.EnvData.ADMIN_SECRET {
return nil, fmt.Errorf(`invalid admin secret`) return res, fmt.Errorf(`invalid admin secret`)
} }
refreshToken, _, _ := utils.CreateAdminAuthToken(enum.RefreshToken, gc) hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
accessToken, expiresAt, _ := utils.CreateAdminAuthToken(enum.AccessToken, gc) if err != nil {
return res, err
currentTime := time.Now().Unix() }
tokenId := fmt.Sprintf("authorizer_admin_%d", currentTime) utils.SetAdminCookie(gc, hashedKey)
session.SetToken(tokenId, accessToken, refreshToken)
utils.SetAdminCookie(gc, accessToken)
res = &model.AdminLoginResponse{ res = &model.AdminLoginResponse{
AccessToken: accessToken, AccessToken: hashedKey,
ExpiresAt: expiresAt,
Message: "admin logged in successfully", Message: "admin logged in successfully",
} }
return res, nil return res, nil

View File

@ -0,0 +1,35 @@
package resolvers
import (
"context"
"fmt"
"github.com/authorizerdev/authorizer/server/constants"
"github.com/authorizerdev/authorizer/server/graph/model"
"github.com/authorizerdev/authorizer/server/utils"
)
func AdminSession(ctx context.Context) (*model.AdminLoginResponse, error) {
gc, err := utils.GinContextFromContext(ctx)
var res *model.AdminLoginResponse
if err != nil {
return res, err
}
if !utils.IsSuperAdmin(gc) {
return res, fmt.Errorf("unauthorized")
}
hashedKey, err := utils.HashPassword(constants.EnvData.ADMIN_SECRET)
if err != nil {
return res, err
}
utils.SetAdminCookie(gc, hashedKey)
res = &model.AdminLoginResponse{
AccessToken: hashedKey,
Message: "admin logged in successfully",
}
return res, nil
}

View File

@ -14,6 +14,7 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/robertkrimen/otto" "github.com/robertkrimen/otto"
"golang.org/x/crypto/bcrypt"
) )
func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) { func CreateAuthToken(user db.User, tokenType enum.TokenType, roles []string) (string, int64, error) {
@ -124,32 +125,8 @@ func VerifyAuthToken(token string) (map[string]interface{}, error) {
return res, nil return res, nil
} }
func CreateAdminAuthToken(tokenType enum.TokenType, c *gin.Context) (string, int64, error) { func CreateAdminAuthToken(tokenType enum.TokenType, c *gin.Context) (string, error) {
t := jwt.New(jwt.GetSigningMethod(constants.EnvData.JWT_TYPE)) return HashPassword(constants.EnvData.ADMIN_SECRET)
expiryBound := time.Hour
if tokenType == enum.RefreshToken {
// expires in 1 year
expiryBound = time.Hour * 8760
}
expiresAt := time.Now().Add(expiryBound).Unix()
customClaims := jwt.MapClaims{
"exp": expiresAt,
"iat": time.Now().Unix(),
"user_agent": GetUserAgent(c.Request),
"ip": GetIP(c.Request),
"role": "authorizer_admin",
"created_at": time.Now().Unix(),
}
t.Claims = customClaims
token, err := t.SignedString([]byte(constants.EnvData.JWT_SECRET))
if err != nil {
return "", 0, err
}
return token, expiresAt, nil
} }
func GetAdminAuthToken(gc *gin.Context) (string, error) { func GetAdminAuthToken(gc *gin.Context) (string, error) {
@ -162,6 +139,11 @@ func GetAdminAuthToken(gc *gin.Context) (string, error) {
} }
token = strings.TrimPrefix(auth, "Bearer ") token = strings.TrimPrefix(auth, "Bearer ")
err = bcrypt.CompareHashAndPassword([]byte(token), []byte(constants.EnvData.ADMIN_SECRET))
if err != nil {
return "", fmt.Errorf(`unauthorized`)
}
} }
return token, nil return token, nil
} }