endpoint refresh token keycloak
This commit is contained in:
@@ -35,5 +35,6 @@ DB_ANTRIAN_SSLMODE=disable
|
|||||||
KEYCLOAK_BASE_URL=https://keycloak.example.com
|
KEYCLOAK_BASE_URL=https://keycloak.example.com
|
||||||
KEYCLOAK_REALM=myrealm
|
KEYCLOAK_REALM=myrealm
|
||||||
KEYCLOAK_AUDIENCE=my-client-id
|
KEYCLOAK_AUDIENCE=my-client-id
|
||||||
|
KEYCLOAK_SECRET_KEY=super-secret
|
||||||
KEYCLOAK_ISSUER=https://keycloak.example.com/realms/myrealm
|
KEYCLOAK_ISSUER=https://keycloak.example.com/realms/myrealm
|
||||||
KEYCLOAK_IS_ENABLE=false
|
KEYCLOAK_IS_ENABLE=false
|
||||||
+32
-3
@@ -504,6 +504,37 @@ const docTemplate = `{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/keycloak/refresh-token": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Keycloak"
|
||||||
|
],
|
||||||
|
"summary": "Requesting new token to keycloak using refresh token",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Valid Refresh Token",
|
||||||
|
"name": "refresh_token",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/shared.BaseResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/shared.BaseErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/reference/diagnosa/": {
|
"/reference/diagnosa/": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -875,8 +906,7 @@ const docTemplate = `{
|
|||||||
"jenisKelamin",
|
"jenisKelamin",
|
||||||
"namaPasien",
|
"namaPasien",
|
||||||
"noKtp",
|
"noKtp",
|
||||||
"noRekamMedis",
|
"noRekamMedis"
|
||||||
"nomorTelepon"
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"alamat": {
|
"alamat": {
|
||||||
@@ -900,7 +930,6 @@ const docTemplate = `{
|
|||||||
},
|
},
|
||||||
"nomorTelepon": {
|
"nomorTelepon": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|||||||
+32
-3
@@ -498,6 +498,37 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/keycloak/refresh-token": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"Keycloak"
|
||||||
|
],
|
||||||
|
"summary": "Requesting new token to keycloak using refresh token",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"type": "string",
|
||||||
|
"description": "Valid Refresh Token",
|
||||||
|
"name": "refresh_token",
|
||||||
|
"in": "query",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/shared.BaseResponse"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"500": {
|
||||||
|
"description": "Internal Server Error",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/shared.BaseErrorResponse"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/reference/diagnosa/": {
|
"/reference/diagnosa/": {
|
||||||
"get": {
|
"get": {
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -869,8 +900,7 @@
|
|||||||
"jenisKelamin",
|
"jenisKelamin",
|
||||||
"namaPasien",
|
"namaPasien",
|
||||||
"noKtp",
|
"noKtp",
|
||||||
"noRekamMedis",
|
"noRekamMedis"
|
||||||
"nomorTelepon"
|
|
||||||
],
|
],
|
||||||
"properties": {
|
"properties": {
|
||||||
"alamat": {
|
"alamat": {
|
||||||
@@ -894,7 +924,6 @@
|
|||||||
},
|
},
|
||||||
"nomorTelepon": {
|
"nomorTelepon": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"minItems": 1,
|
|
||||||
"items": {
|
"items": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
|
|||||||
+20
-2
@@ -94,7 +94,6 @@ definitions:
|
|||||||
nomorTelepon:
|
nomorTelepon:
|
||||||
items:
|
items:
|
||||||
type: string
|
type: string
|
||||||
minItems: 1
|
|
||||||
type: array
|
type: array
|
||||||
tanggalLahir:
|
tanggalLahir:
|
||||||
type: string
|
type: string
|
||||||
@@ -105,7 +104,6 @@ definitions:
|
|||||||
- namaPasien
|
- namaPasien
|
||||||
- noKtp
|
- noKtp
|
||||||
- noRekamMedis
|
- noRekamMedis
|
||||||
- nomorTelepon
|
|
||||||
type: object
|
type: object
|
||||||
antrianoperasi.PasienOperasi:
|
antrianoperasi.PasienOperasi:
|
||||||
properties:
|
properties:
|
||||||
@@ -747,6 +745,26 @@ paths:
|
|||||||
summary: Get Table Antrian per Sub Spesialis
|
summary: Get Table Antrian per Sub Spesialis
|
||||||
tags:
|
tags:
|
||||||
- Dashboard
|
- Dashboard
|
||||||
|
/keycloak/refresh-token:
|
||||||
|
post:
|
||||||
|
parameters:
|
||||||
|
- description: Valid Refresh Token
|
||||||
|
in: query
|
||||||
|
name: refresh_token
|
||||||
|
required: true
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/shared.BaseResponse'
|
||||||
|
"500":
|
||||||
|
description: Internal Server Error
|
||||||
|
schema:
|
||||||
|
$ref: '#/definitions/shared.BaseErrorResponse'
|
||||||
|
summary: Requesting new token to keycloak using refresh token
|
||||||
|
tags:
|
||||||
|
- Keycloak
|
||||||
/reference/diagnosa/:
|
/reference/diagnosa/:
|
||||||
get:
|
get:
|
||||||
parameters:
|
parameters:
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ func LoadConfig() *Config {
|
|||||||
Realm: getEnv("KEYCLOAK_REALM", "sandbox"),
|
Realm: getEnv("KEYCLOAK_REALM", "sandbox"),
|
||||||
Audience: getEnv("KEYCLOAK_AUDIENCE", "akbar-test"),
|
Audience: getEnv("KEYCLOAK_AUDIENCE", "akbar-test"),
|
||||||
Issuer: getEnv("KEYCLOAK_ISSUER", "https://auth.rssa.top/realms/sandbox"),
|
Issuer: getEnv("KEYCLOAK_ISSUER", "https://auth.rssa.top/realms/sandbox"),
|
||||||
|
SecretKey: getEnv("KEYCLOAK_SECRET_KEY", ""),
|
||||||
IsEnabled: getEnvAsBool("KEYCLOAK_IS_ENABLE", false),
|
IsEnabled: getEnvAsBool("KEYCLOAK_IS_ENABLE", false),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,5 +55,6 @@ type KeycloakConfig struct {
|
|||||||
Realm string
|
Realm string
|
||||||
Audience string
|
Audience string
|
||||||
Issuer string
|
Issuer string
|
||||||
|
SecretKey string
|
||||||
IsEnabled bool
|
IsEnabled bool
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
package keycloak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
baseResponse "antrian-operasi/internal/shared"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeycloakHandler struct {
|
||||||
|
service IKeycloakService
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeycloakHandler(service IKeycloakService) KeycloakHandler {
|
||||||
|
return KeycloakHandler{service}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTokenByRefreshCode godoc
|
||||||
|
// @Summary Requesting new token to keycloak using refresh token
|
||||||
|
// @Tags Keycloak
|
||||||
|
// @Param refresh_token query string true "Valid Refresh Token"
|
||||||
|
// @Success 200 {object} shared.BaseResponse
|
||||||
|
// @Failure 500 {object} shared.BaseErrorResponse
|
||||||
|
// @Router /keycloak/refresh-token [post]
|
||||||
|
func (h KeycloakHandler) GetTokenByRefreshCode(c *gin.Context) {
|
||||||
|
refreshToken := c.Query("refresh_token")
|
||||||
|
resp, err := h.service.FetchTokenUsingRefreshToken(c, refreshToken)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errorResponse := baseResponse.BaseErrorResponse{
|
||||||
|
Success: false,
|
||||||
|
Code: 400,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusInternalServerError, errorResponse)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
response := baseResponse.ToBaseResponse(
|
||||||
|
resp,
|
||||||
|
true,
|
||||||
|
200,
|
||||||
|
"success refreshing token")
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, response)
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package keycloak
|
||||||
|
|
||||||
|
type TokenErrorResponse struct {
|
||||||
|
Error string `json:"error"`
|
||||||
|
ErrorDescription string `json:"error_description"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenResponse struct {
|
||||||
|
AccessToken string `json:"access_token"`
|
||||||
|
ExpiresIn int `json:"expires_in"`
|
||||||
|
RefreshToken string `json:"refresh_token"`
|
||||||
|
RefreshExpiresIn int `json:"refresh_expires_in"`
|
||||||
|
TokenType string `json:"token_type"`
|
||||||
|
Scope string `json:"scope"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package keycloak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"antrian-operasi/internal/config"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RegisterRoutes(r *gin.RouterGroup, cfg config.KeycloakConfig) {
|
||||||
|
keycloakService := NewKeycloakService(cfg)
|
||||||
|
keycloakHandler := NewKeycloakHandler(keycloakService)
|
||||||
|
|
||||||
|
r.POST("/refresh-token", keycloakHandler.GetTokenByRefreshCode)
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
package keycloak
|
||||||
|
|
||||||
|
import (
|
||||||
|
"antrian-operasi/internal/config"
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type IKeycloakService interface {
|
||||||
|
FetchTokenUsingRefreshToken(c context.Context, refreshToken string) (*TokenResponse, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type KeycloakService struct {
|
||||||
|
config config.KeycloakConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewKeycloakService(cfg config.KeycloakConfig) IKeycloakService {
|
||||||
|
return KeycloakService{cfg}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s KeycloakService) FetchTokenUsingRefreshToken(c context.Context, refreshToken string) (*TokenResponse, error) {
|
||||||
|
refreshTokenUrl := s.config.BaseUrl + "/realms/" + s.config.Realm + "/protocol/openid-connect/token"
|
||||||
|
bodyRequest := url.Values{}
|
||||||
|
bodyRequest.Set("grant_type", "refresh_token")
|
||||||
|
bodyRequest.Set("client_id", s.config.Audience)
|
||||||
|
bodyRequest.Set("client_secret", s.config.SecretKey)
|
||||||
|
bodyRequest.Set("refresh_token", refreshToken)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
req, err := http.NewRequestWithContext(c, http.MethodPost, refreshTokenUrl, strings.NewReader(bodyRequest.Encode()))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error request token %s", err)
|
||||||
|
}
|
||||||
|
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("error response %s", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
var errResp TokenErrorResponse
|
||||||
|
json.NewDecoder(resp.Body).Decode(&errResp)
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("Keycloak error : %s - %s", errResp.Error, errResp.ErrorDescription)
|
||||||
|
}
|
||||||
|
|
||||||
|
var tokenResp TokenResponse
|
||||||
|
err = json.NewDecoder(resp.Body).Decode(&tokenResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &tokenResp, nil
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"antrian-operasi/internal/database"
|
"antrian-operasi/internal/database"
|
||||||
antrianoperasi "antrian-operasi/internal/domain/antrian_operasi"
|
antrianoperasi "antrian-operasi/internal/domain/antrian_operasi"
|
||||||
"antrian-operasi/internal/domain/dashboard"
|
"antrian-operasi/internal/domain/dashboard"
|
||||||
|
"antrian-operasi/internal/domain/keycloak"
|
||||||
"antrian-operasi/internal/domain/reference/diagnosa"
|
"antrian-operasi/internal/domain/reference/diagnosa"
|
||||||
"antrian-operasi/internal/domain/reference/dokter"
|
"antrian-operasi/internal/domain/reference/dokter"
|
||||||
"antrian-operasi/internal/domain/reference/kategori"
|
"antrian-operasi/internal/domain/reference/kategori"
|
||||||
@@ -40,13 +41,13 @@ func RegisterRoutes(cfg *config.Config, dbService database.Service) *gin.Engine
|
|||||||
log.Fatalf("Unable to initiate keycloak auth")
|
log.Fatalf("Unable to initiate keycloak auth")
|
||||||
}
|
}
|
||||||
|
|
||||||
api := router.Group("/api", authKeycloak)
|
api := router.Group("/api")
|
||||||
|
|
||||||
antrian := api.Group("/antrian-operasi")
|
antrian := api.Group("/antrian-operasi", authKeycloak)
|
||||||
{
|
{
|
||||||
antrianoperasi.RegisterRoutes(antrian, dbService)
|
antrianoperasi.RegisterRoutes(antrian, dbService)
|
||||||
}
|
}
|
||||||
reference := api.Group("/reference")
|
reference := api.Group("/reference", authKeycloak)
|
||||||
{
|
{
|
||||||
kategori.RegisterRoutes(reference, dbService)
|
kategori.RegisterRoutes(reference, dbService)
|
||||||
spesialis.RegisterRoutes(reference, dbService)
|
spesialis.RegisterRoutes(reference, dbService)
|
||||||
@@ -55,10 +56,14 @@ func RegisterRoutes(cfg *config.Config, dbService database.Service) *gin.Engine
|
|||||||
diagnosa.RegisterRoutes(reference, dbService)
|
diagnosa.RegisterRoutes(reference, dbService)
|
||||||
tindakan.RegisterRoutes(reference, dbService)
|
tindakan.RegisterRoutes(reference, dbService)
|
||||||
}
|
}
|
||||||
dboard := api.Group("dashboard")
|
dboard := api.Group("dashboard", authKeycloak)
|
||||||
{
|
{
|
||||||
dashboard.RegisterRoutes(dboard, dbService)
|
dashboard.RegisterRoutes(dboard, dbService)
|
||||||
}
|
}
|
||||||
|
kc := api.Group("/keycloak")
|
||||||
|
{
|
||||||
|
keycloak.RegisterRoutes(kc, cfg.Keycloak)
|
||||||
|
}
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user