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)
|
userHandler := handler.NewUserHandler(userStore)
|
||||||
loginHandler := handler.NewLoginHandler(userStore)
|
loginHandler := handler.NewLoginHandler(userStore)
|
||||||
serviceHandler := handler.NewServiceHandler(serviceStore)
|
serviceHandler := handler.NewServiceHandler(serviceStore)
|
||||||
|
refreshHandler := handler.NewRefreshHandler(userStore, serviceStore)
|
||||||
|
|
||||||
router := chi.NewRouter()
|
router := chi.NewRouter()
|
||||||
|
|
||||||
@@ -91,6 +92,7 @@ func main() {
|
|||||||
router.Method("Post", endpoint.Login, http.HandlerFunc(loginHandler.Login))
|
router.Method("Post", endpoint.Login, http.HandlerFunc(loginHandler.Login))
|
||||||
router.Method("Post", endpoint.CreateServiceUser, http.HandlerFunc(serviceHandler.Register))
|
router.Method("Post", endpoint.CreateServiceUser, http.HandlerFunc(serviceHandler.Register))
|
||||||
router.Method("Post", endpoint.LoginServiceUser, http.HandlerFunc(serviceHandler.Login))
|
router.Method("Post", endpoint.LoginServiceUser, http.HandlerFunc(serviceHandler.Login))
|
||||||
|
router.Method("Post", endpoint.TokenRefresh, http.HandlerFunc(refreshHandler.Refresh))
|
||||||
|
|
||||||
router.Method("GET", "/swagger/*", httpSwagger.Handler(
|
router.Method("GET", "/swagger/*", httpSwagger.Handler(
|
||||||
httpSwagger.URL(fmt.Sprintf("http://localhost:%s/swagger/doc.json", config.Port)),
|
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)
|
userHandler := handler.NewUserHandler(userStore)
|
||||||
loginHandler := handler.NewLoginHandler(userStore)
|
loginHandler := handler.NewLoginHandler(userStore)
|
||||||
serviceHandler := handler.NewServiceHandler(serviceStore)
|
serviceHandler := handler.NewServiceHandler(serviceStore)
|
||||||
|
refreshHandler := handler.NewRefreshHandler(userStore, serviceStore)
|
||||||
|
|
||||||
testRouter = mux.NewRouter()
|
testRouter = mux.NewRouter()
|
||||||
testRouter.HandleFunc(endpoint.Register, userHandler.Register).Methods("POST")
|
testRouter.HandleFunc(endpoint.Register, userHandler.Register).Methods("POST")
|
||||||
testRouter.HandleFunc(endpoint.Login, loginHandler.Login).Methods("POST")
|
testRouter.HandleFunc(endpoint.Login, loginHandler.Login).Methods("POST")
|
||||||
testRouter.HandleFunc(endpoint.CreateServiceUser, serviceHandler.Register).Methods("POST")
|
testRouter.HandleFunc(endpoint.CreateServiceUser, serviceHandler.Register).Methods("POST")
|
||||||
testRouter.HandleFunc(endpoint.LoginServiceUser, serviceHandler.Login).Methods("POST")
|
testRouter.HandleFunc(endpoint.LoginServiceUser, serviceHandler.Login).Methods("POST")
|
||||||
|
testRouter.HandleFunc(endpoint.TokenRefresh, refreshHandler.Refresh).Methods("POST")
|
||||||
|
|
||||||
code := m.Run()
|
code := m.Run()
|
||||||
os.Exit(code)
|
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": {
|
"/service/register": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"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": {
|
"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": {
|
"handler.RegisterResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"token.Login": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"/service/register": {
|
||||||
"post": {
|
"post": {
|
||||||
"security": [
|
"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": {
|
"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": {
|
"handler.RegisterResponse": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"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": {
|
"token.Login": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
|
|||||||
@@ -16,6 +16,20 @@ definitions:
|
|||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
handler.RegisterResponse:
|
||||||
properties:
|
properties:
|
||||||
data:
|
data:
|
||||||
@@ -59,6 +73,22 @@ definitions:
|
|||||||
message:
|
message:
|
||||||
type: string
|
type: string
|
||||||
type: object
|
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:
|
token.Login:
|
||||||
properties:
|
properties:
|
||||||
access_token:
|
access_token:
|
||||||
@@ -150,6 +180,34 @@ paths:
|
|||||||
summary: Register user
|
summary: Register user
|
||||||
tags:
|
tags:
|
||||||
- users
|
- 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:
|
/service/register:
|
||||||
post:
|
post:
|
||||||
consumes:
|
consumes:
|
||||||
@@ -182,6 +240,38 @@ paths:
|
|||||||
summary: Register service user
|
summary: Register service user
|
||||||
tags:
|
tags:
|
||||||
- service users
|
- 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:
|
securityDefinitions:
|
||||||
BearerAuth:
|
BearerAuth:
|
||||||
description: JWT Bearer Token
|
description: JWT Bearer Token
|
||||||
|
|||||||
@@ -5,3 +5,4 @@ const Register = "/api/v1/register"
|
|||||||
const Login = "/api/v1/login"
|
const Login = "/api/v1/login"
|
||||||
const CreateServiceUser = "/api/v1/service/register"
|
const CreateServiceUser = "/api/v1/service/register"
|
||||||
const LoginServiceUser = "/api/v1/service/login"
|
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")
|
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"
|
"fmt"
|
||||||
|
|
||||||
"git.kundeng.us/phoenix/textsender-models/pkg/user"
|
"git.kundeng.us/phoenix/textsender-models/pkg/user"
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/jackc/pgx/v5"
|
"github.com/jackc/pgx/v5"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
)
|
)
|
||||||
@@ -12,6 +13,7 @@ import (
|
|||||||
type ServiceStore interface {
|
type ServiceStore interface {
|
||||||
CheckWithUsername(ctx context.Context, username string) (bool, error)
|
CheckWithUsername(ctx context.Context, username string) (bool, error)
|
||||||
GetWithUsername(ctx context.Context, username string) (*user.ServiceUser, 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
|
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 {
|
func (s *PGServiceStore) Create(ctx context.Context, serviceUser *user.ServiceUser) error {
|
||||||
query := `
|
query := `
|
||||||
INSERT INTO service_users (username, passphrase)
|
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) {
|
func (t *TokenGenerator) generateClaims(usr any, role string, issuedAt time.Time, expiredAt time.Time) (*token.Claims, error) {
|
||||||
var id uuid.UUID
|
var id uuid.UUID
|
||||||
switch val := usr.(type) {
|
switch val := usr.(type) {
|
||||||
|
|||||||
Reference in New Issue
Block a user