tsk-20: Obtain refresh token (#24)
Closes #20 Reviewed-on: phoenix/textsender-auth#24 Co-authored-by: phoenix <kundeng00@pm.me> Co-committed-by: phoenix <kundeng00@pm.me>
This commit is contained in:
@@ -79,6 +79,7 @@ func main() {
|
||||
userHandler := handler.NewUserHandler(userStore)
|
||||
loginHandler := handler.NewLoginHandler(userStore)
|
||||
serviceHandler := handler.NewServiceHandler(serviceStore)
|
||||
refreshHandler := handler.NewRefreshHandler(userStore, serviceStore)
|
||||
|
||||
router := chi.NewRouter()
|
||||
|
||||
@@ -91,6 +92,7 @@ func main() {
|
||||
router.Method("Post", endpoint.Login, http.HandlerFunc(loginHandler.Login))
|
||||
router.Method("Post", endpoint.CreateServiceUser, http.HandlerFunc(serviceHandler.Register))
|
||||
router.Method("Post", endpoint.LoginServiceUser, http.HandlerFunc(serviceHandler.Login))
|
||||
router.Method("Post", endpoint.TokenRefresh, http.HandlerFunc(refreshHandler.Refresh))
|
||||
|
||||
router.Method("GET", "/swagger/*", httpSwagger.Handler(
|
||||
httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.Port)),
|
||||
|
||||
@@ -43,12 +43,14 @@ func TestMain(m *testing.M) {
|
||||
userHandler := handler.NewUserHandler(userStore)
|
||||
loginHandler := handler.NewLoginHandler(userStore)
|
||||
serviceHandler := handler.NewServiceHandler(serviceStore)
|
||||
refreshHandler := handler.NewRefreshHandler(userStore, serviceStore)
|
||||
|
||||
testRouter = mux.NewRouter()
|
||||
testRouter.HandleFunc(endpoint.Register, userHandler.Register).Methods("POST")
|
||||
testRouter.HandleFunc(endpoint.Login, loginHandler.Login).Methods("POST")
|
||||
testRouter.HandleFunc(endpoint.CreateServiceUser, serviceHandler.Register).Methods("POST")
|
||||
testRouter.HandleFunc(endpoint.LoginServiceUser, serviceHandler.Login).Methods("POST")
|
||||
testRouter.HandleFunc(endpoint.TokenRefresh, refreshHandler.Refresh).Methods("POST")
|
||||
|
||||
code := m.Run()
|
||||
os.Exit(code)
|
||||
|
||||
+143
@@ -117,6 +117,51 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/service/login": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Servce login and be given an access token (requires JWT)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"service users"
|
||||
],
|
||||
"summary": "Service login",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Data to obtain a service token",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ServiceLoginRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ServiceLoginResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ServiceLoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/service/register": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -167,6 +212,57 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/token/refresh": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Refresh token endpoint (requires JWT)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"refresh"
|
||||
],
|
||||
"summary": "Obtain a refresh token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Data to refresh token",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RefreshRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RefreshResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RefreshResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RefreshResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -195,6 +291,28 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.RefreshRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.RefreshResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/token.Login"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.RegisterResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -262,6 +380,31 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.ServiceLoginRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"passphrase": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.ServiceLoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/token.Login"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"token.Login": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -111,6 +111,51 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/service/login": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Servce login and be given an access token (requires JWT)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"service users"
|
||||
],
|
||||
"summary": "Service login",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Data to obtain a service token",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ServiceLoginRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ServiceLoginResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.ServiceLoginResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/service/register": {
|
||||
"post": {
|
||||
"security": [
|
||||
@@ -161,6 +206,57 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/token/refresh": {
|
||||
"post": {
|
||||
"security": [
|
||||
{
|
||||
"BearerAuth": []
|
||||
}
|
||||
],
|
||||
"description": "Refresh token endpoint (requires JWT)",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"refresh"
|
||||
],
|
||||
"summary": "Obtain a refresh token",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Data to refresh token",
|
||||
"name": "request",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RefreshRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RefreshResponse"
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "Bad Request",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RefreshResponse"
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "Internal Server Error",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/handler.RefreshResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
@@ -189,6 +285,28 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.RefreshRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"access_token": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.RefreshResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/token.Login"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.RegisterResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -256,6 +374,31 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.ServiceLoginRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"passphrase": {
|
||||
"type": "string"
|
||||
},
|
||||
"username": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"handler.ServiceLoginResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/token.Login"
|
||||
}
|
||||
},
|
||||
"message": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"token.Login": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -16,6 +16,20 @@ definitions:
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
handler.RefreshRequest:
|
||||
properties:
|
||||
access_token:
|
||||
type: string
|
||||
type: object
|
||||
handler.RefreshResponse:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/token.Login'
|
||||
type: array
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
handler.RegisterResponse:
|
||||
properties:
|
||||
data:
|
||||
@@ -59,6 +73,22 @@ definitions:
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
handler.ServiceLoginRequest:
|
||||
properties:
|
||||
passphrase:
|
||||
type: string
|
||||
username:
|
||||
type: string
|
||||
type: object
|
||||
handler.ServiceLoginResponse:
|
||||
properties:
|
||||
data:
|
||||
items:
|
||||
$ref: '#/definitions/token.Login'
|
||||
type: array
|
||||
message:
|
||||
type: string
|
||||
type: object
|
||||
token.Login:
|
||||
properties:
|
||||
access_token:
|
||||
@@ -150,6 +180,34 @@ paths:
|
||||
summary: Register user
|
||||
tags:
|
||||
- users
|
||||
/service/login:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Servce login and be given an access token (requires JWT)
|
||||
parameters:
|
||||
- description: Data to obtain a service token
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handler.ServiceLoginRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.ServiceLoginResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/handler.ServiceLoginResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Service login
|
||||
tags:
|
||||
- service users
|
||||
/service/register:
|
||||
post:
|
||||
consumes:
|
||||
@@ -182,6 +240,38 @@ paths:
|
||||
summary: Register service user
|
||||
tags:
|
||||
- service users
|
||||
/token/refresh:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Refresh token endpoint (requires JWT)
|
||||
parameters:
|
||||
- description: Data to refresh token
|
||||
in: body
|
||||
name: request
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RefreshRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RefreshResponse'
|
||||
"400":
|
||||
description: Bad Request
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RefreshResponse'
|
||||
"500":
|
||||
description: Internal Server Error
|
||||
schema:
|
||||
$ref: '#/definitions/handler.RefreshResponse'
|
||||
security:
|
||||
- BearerAuth: []
|
||||
summary: Obtain a refresh token
|
||||
tags:
|
||||
- refresh
|
||||
securityDefinitions:
|
||||
BearerAuth:
|
||||
description: JWT Bearer Token
|
||||
|
||||
@@ -5,3 +5,4 @@ const Register = "/api/v1/register"
|
||||
const Login = "/api/v1/login"
|
||||
const CreateServiceUser = "/api/v1/service/register"
|
||||
const LoginServiceUser = "/api/v1/service/login"
|
||||
const TokenRefresh = "/api/v1/token/refresh"
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-models/pkg/token"
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/config"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/model"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/store"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/utility"
|
||||
)
|
||||
|
||||
type RefreshHandler struct {
|
||||
UserStore model.UserStore
|
||||
ServiceStore store.ServiceStore
|
||||
}
|
||||
|
||||
func NewRefreshHandler(userStore model.UserStore, serviceStore store.ServiceStore) *RefreshHandler {
|
||||
return &RefreshHandler{UserStore: userStore, ServiceStore: serviceStore}
|
||||
}
|
||||
|
||||
type RefreshRequest struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
type RefreshResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data []*token.Login `json:"data"`
|
||||
}
|
||||
|
||||
// Refresh godoc
|
||||
// @Summary Obtain a refresh token
|
||||
// @Description Refresh token endpoint (requires JWT)
|
||||
// @Tags refresh
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Security BearerAuth
|
||||
// @Param request body RefreshRequest true "Data to refresh token"
|
||||
// @Success 200 {object} RefreshResponse
|
||||
// @Failure 400 {object} RefreshResponse
|
||||
// @Failure 500 {object} RefreshResponse
|
||||
// @Router /token/refresh [post]
|
||||
func (rh *RefreshHandler) Refresh(w http.ResponseWriter, r *http.Request) {
|
||||
var req RefreshRequest
|
||||
if err := ExtractFromRequest(r, &req); err != nil {
|
||||
http.Error(w, "Invalid JSON: "+err.Error(), http.StatusBadRequest)
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
var statusCode int
|
||||
var resp RefreshResponse
|
||||
|
||||
secretKey := config.GetSecretKey()
|
||||
tokGen := utility.TokenGenerator{}
|
||||
tokGen.SetSecretKey(secretKey)
|
||||
tokGen.SetHourOffset(12)
|
||||
if verified, err := tokGen.VerifyToken(req.AccessToken); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
if verified {
|
||||
if id, err := tokGen.ExtractIdFromToken(req.AccessToken); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
ctx := r.Context()
|
||||
if usr, err := rh.UserStore.GetUserByID(ctx, id); err != nil || usr == nil {
|
||||
if serviceUsr, err := rh.ServiceStore.GetWithId(ctx, id); err != nil || serviceUsr == nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
if myToken, err := tokGen.GenerateToken(serviceUsr); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
statusCode = http.StatusOK
|
||||
resp.Data = append(resp.Data, myToken)
|
||||
resp.Message = "Successful"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if myToken, err := tokGen.GenerateToken(usr); err != nil {
|
||||
statusCode = http.StatusInternalServerError
|
||||
resp.Message = err.Error()
|
||||
} else {
|
||||
statusCode = http.StatusOK
|
||||
resp.Data = append(resp.Data, myToken)
|
||||
resp.Message = "Successful"
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
statusCode = http.StatusBadRequest
|
||||
resp.Message = "Unverified"
|
||||
}
|
||||
}
|
||||
|
||||
RespondWithJson(w, statusCode, &resp)
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-models/pkg/user"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/handler/endpoint"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/store/mock"
|
||||
"git.kundeng.us/phoenix/textsender-auth/internal/utility"
|
||||
)
|
||||
|
||||
func TestRefreshTokenWithMock(t *testing.T) {
|
||||
var serviceUser user.ServiceUser
|
||||
var hashedPassword string
|
||||
var err error
|
||||
unhashed := "9328nr29nudx3292m320!"
|
||||
hashing := utility.HashMash{Password: unhashed}
|
||||
if hashedPassword, err = hashing.HashPassword(); err != nil {
|
||||
assert.NoError(t, err, "Error hashing password: %v", err)
|
||||
} else {
|
||||
serviceUser.Passphrase = hashedPassword
|
||||
}
|
||||
serviceUser.Username = "swoon"
|
||||
ctx := t.Context()
|
||||
mockStore := mock.NewMockServiceUserStore()
|
||||
userStore := mock.NewMockUserStore()
|
||||
|
||||
if err := mockStore.Create(ctx, &serviceUser); err != nil {
|
||||
assert.NoError(t, err, "Error creating service user: %v", err)
|
||||
}
|
||||
|
||||
handler := NewServiceHandler(mockStore)
|
||||
testService := ServiceLoginRequest{Username: serviceUser.Username, Passphrase: unhashed}
|
||||
jsonValue, err := json.Marshal(testService)
|
||||
assert.NoError(t, err, "Error marshaling request")
|
||||
|
||||
req, _ := http.NewRequest("POST", endpoint.LoginServiceUser, strings.NewReader(string(jsonValue)))
|
||||
rr := httptest.NewRecorder()
|
||||
|
||||
handler.Login(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
|
||||
var response ServiceLoginResponse
|
||||
err = json.Unmarshal(rr.Body.Bytes(), &response)
|
||||
assert.NoError(t, err)
|
||||
|
||||
accessToken := response.Data[0].AccessToken
|
||||
|
||||
testReq := RefreshRequest{AccessToken: accessToken}
|
||||
jsonValue, err = json.Marshal(testReq)
|
||||
assert.NoError(t, err, "Error marshaling request")
|
||||
|
||||
newHandler := NewRefreshHandler(userStore, mockStore)
|
||||
req, _ = http.NewRequest("POST", endpoint.TokenRefresh, strings.NewReader(string(jsonValue)))
|
||||
rr = httptest.NewRecorder()
|
||||
|
||||
newHandler.Refresh(rr, req)
|
||||
assert.Equal(t, http.StatusOK, rr.Code)
|
||||
}
|
||||
@@ -84,3 +84,20 @@ func (m *MockServiceUserStore) GetWithUsername(ctx context.Context, username str
|
||||
return nil, fmt.Errorf("User not found")
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockServiceUserStore) GetWithId(ctx context.Context, id uuid.UUID) (*user.ServiceUser, error) {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
|
||||
if m.Error != nil {
|
||||
return nil, m.Error
|
||||
}
|
||||
|
||||
for _, serviceUser := range m.ServiceUsers {
|
||||
if serviceUser.Id == id {
|
||||
return serviceUser, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"fmt"
|
||||
|
||||
"git.kundeng.us/phoenix/textsender-models/pkg/user"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
@@ -12,6 +13,7 @@ import (
|
||||
type ServiceStore interface {
|
||||
CheckWithUsername(ctx context.Context, username string) (bool, error)
|
||||
GetWithUsername(ctx context.Context, username string) (*user.ServiceUser, error)
|
||||
GetWithId(ctx context.Context, id uuid.UUID) (*user.ServiceUser, error)
|
||||
Create(ctx context.Context, serviceUser *user.ServiceUser) error
|
||||
}
|
||||
|
||||
@@ -53,6 +55,21 @@ func (s *PGServiceStore) GetWithUsername(ctx context.Context, username string) (
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PGServiceStore) GetWithId(ctx context.Context, id uuid.UUID) (*user.ServiceUser, error) {
|
||||
var serviceUser user.ServiceUser
|
||||
query := `SELECT id, username, passphrase, created FROM service_users WHERE id = $1`
|
||||
|
||||
if err := s.db.QueryRow(ctx, query, id).Scan(&serviceUser.Id, &serviceUser.Username, &serviceUser.Passphrase, &serviceUser.Created); err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
} else {
|
||||
return nil, fmt.Errorf("Error querying row: %v", err)
|
||||
}
|
||||
} else {
|
||||
return &serviceUser, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PGServiceStore) Create(ctx context.Context, serviceUser *user.ServiceUser) error {
|
||||
query := `
|
||||
INSERT INTO service_users (username, passphrase)
|
||||
|
||||
@@ -53,6 +53,66 @@ func (t *TokenGenerator) GenerateToken(usr any) (*token.Login, error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TokenGenerator) VerifyToken(accessToken string) (bool, error) {
|
||||
clms := &token.Claims{}
|
||||
|
||||
if tken, err := t.parseTokenWithClaims(accessToken, clms); err != nil {
|
||||
return false, nil
|
||||
} else {
|
||||
if tken.Valid {
|
||||
if clms != nil {
|
||||
if clms.UserId != uuid.Nil {
|
||||
return true, nil
|
||||
} else {
|
||||
return false, fmt.Errorf("User Id was not set")
|
||||
}
|
||||
} else {
|
||||
return false, fmt.Errorf("Claims not parsed")
|
||||
}
|
||||
} else {
|
||||
return false, fmt.Errorf("Invalid access token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TokenGenerator) ExtractIdFromToken(accessToken string) (uuid.UUID, error) {
|
||||
clms := &token.Claims{}
|
||||
|
||||
if tken, err := t.parseTokenWithClaims(accessToken, clms); err != nil {
|
||||
return uuid.Nil, nil
|
||||
} else {
|
||||
if tken.Valid {
|
||||
if clms != nil {
|
||||
if clms.UserId != uuid.Nil {
|
||||
return clms.UserId, nil
|
||||
} else {
|
||||
return uuid.Nil, fmt.Errorf("User Id was not set")
|
||||
}
|
||||
} else {
|
||||
return uuid.Nil, fmt.Errorf("Claims not parsed")
|
||||
}
|
||||
} else {
|
||||
return uuid.Nil, fmt.Errorf("Invalid access token")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func (t *TokenGenerator) parseTokenWithClaims(accessToken string, claims *token.Claims) (*jwt.Token, error) {
|
||||
tken, err := jwt.ParseWithClaims(accessToken, claims, func(tken *jwt.Token) (any, error) {
|
||||
if _, ok := tken.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", tken.Header["alg"])
|
||||
}
|
||||
return t.SecretKey, nil
|
||||
}, jwt.WithValidMethods([]string{jwt.SigningMethodHS256.Alg()}))
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
return tken, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (t *TokenGenerator) generateClaims(usr any, role string, issuedAt time.Time, expiredAt time.Time) (*token.Claims, error) {
|
||||
var id uuid.UUID
|
||||
switch val := usr.(type) {
|
||||
|
||||
Reference in New Issue
Block a user