Skip to content

Commit

Permalink
Merge pull request #8 from jrione/feat/login
Browse files Browse the repository at this point in the history
Feat/login
  • Loading branch information
jrione authored Mar 1, 2024
2 parents 6dd42f9 + 8300ae9 commit 49ac448
Show file tree
Hide file tree
Showing 12 changed files with 302 additions and 54 deletions.
1 change: 1 addition & 0 deletions config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"x-api-key": "njirlah",
"access_token_secret": "BJIRSECRET",
"access_token_expiry": 24,
"refresh_token_secret": "BJIRREFRESH",
"refresh_token_expiry": 168
},
"database":{
Expand Down
28 changes: 26 additions & 2 deletions config/jwt.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,30 @@ func IsAuthorized(authToken string, secret string) (bool, error) {

}

func IsExpired(authToken, secret string) (bool, string) {
return false, ""
func GetUsernameFromClaim(authToken string, secret string) (user string, err error) {
token, err := jwt.Parse(authToken, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("Cannot signing method with algorithm: %v", token.Header["alg"])
}
return []byte(secret), nil
})
if err != nil {
if validErr, ok := err.(*jwt.ValidationError); ok {
if validErr.Errors&jwt.ValidationErrorMalformed != 0 {
log.Printf("ErrTokenMalformed: %v", err)
return "", err
} else if validErr.Errors&(jwt.ValidationErrorExpired|jwt.ValidationErrorNotValidYet) != 0 {
log.Printf("ErrTokenExpired: %v", err)
return "", err
}
}
log.Printf("Error parsing token: %v", err)
return "false", err
}
claims := token.Claims.(jwt.MapClaims)
return claims["username"].(string), nil
}

func IsExpired(authToken, secret string) (string, string, bool) {
return "", "", false
}
120 changes: 92 additions & 28 deletions controller/login_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@ import (
)

type LoginController struct {
LoginUseCase domain.LoginUseCase
Env *config.Config
LoginUseCase domain.LoginUseCase
RefreshTokenUseCase domain.RefreshTokenUseCase
Env *config.Config
}

func checkHashPass(hashed string, realPass string) bool {
Expand Down Expand Up @@ -46,30 +47,65 @@ func (l LoginController) Login(gctx *gin.Context) {
return
}

accessToken, err := l.LoginUseCase.CreateAccessToken(data, l.Env.Server.AccessTokenSecret, l.Env.Server.AccessTokenExpiry)
hasRefreshTokenData, err := l.RefreshTokenUseCase.GetRefreshToken(gctx, data.Username)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err,
})
return
}
refreshToken, err := l.LoginUseCase.CreateAccessToken(data, l.Env.Server.RefreshTokenSecret, l.Env.Server.RefreshTokenExpiry)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err,
"error": "Error get refresh token",
"cause": err.Error(),
})
return
}

resp := &domain.LoginResponse{
AccessToken: accessToken,
RefreshToken: refreshToken,
if hasRefreshTokenData == nil {

Check failure on line 60 in controller/login_controller.go

View workflow job for this annotation

GitHub Actions / build

syntax error: unexpected if, expected expression
resp.AccessToken, err = l.LoginUseCase.CreateAccessToken(data, l.Env.Server.AccessTokenSecret, l.Env.Server.AccessTokenExpiry)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err,
})
return
}

refreshToken, err := l.LoginUseCase.CreateRefreshToken(data, l.Env.Server.RefreshTokenSecret, l.Env.Server.RefreshTokenExpiry)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err,
})
return
}
refreshTokenData := domain.RefreshTokenData{
Username: data.Username,
RefreshToken: refreshToken,
}

err = l.RefreshTokenUseCase.StoreRefreshToken(gctx, refreshTokenData)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err.Error(),
})
return
}

} else {
gctx.Request.Method = "GET"
gctx.Writer.Header().Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im5qaXIgICAgICAgICAgICAgICAgIiwiZXhwIjoxNzA5MzAwNjAxfQ.spYgQzKUSq5V7soS2aAXrF7qx-gCCJKLZ-9WN8JDjLY")
gctx.Redirect(http.StatusSeeOther, "/user")
gctx.Next()
return
}

gctx.JSON(http.StatusOK, resp)
}

func (l LoginController) RefreshToken(gctx *gin.Context) {
gctx.JSON(http.StatusOK, gin.H{
"status": "200",
})
}

func (l LoginController) GetLogin(gctx *gin.Context) {

data, err := l.LoginUseCase.CheckUser(gctx, "njir")
Expand All @@ -81,27 +117,55 @@ func (l LoginController) GetLogin(gctx *gin.Context) {
return
}

accessToken, err := l.LoginUseCase.CreateAccessToken(data, l.Env.Server.AccessTokenSecret, l.Env.Server.AccessTokenExpiry)
hasRefreshTokenData, err := l.RefreshTokenUseCase.GetRefreshToken(gctx, data.Username)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err,
"error": "Error get refresh token",
"cause": err.Error(),
})
return
}

refreshToken, err := l.LoginUseCase.CreateAccessToken(data, l.Env.Server.AccessTokenSecret, l.Env.Server.AccessTokenExpiry)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err,
})
resp := &domain.LoginResponse{}
if hasRefreshTokenData == nil {
resp.AccessToken, err = l.LoginUseCase.CreateAccessToken(data, l.Env.Server.AccessTokenSecret, l.Env.Server.AccessTokenExpiry)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err,
})
return
}

refreshToken, err := l.LoginUseCase.CreateRefreshToken(data, l.Env.Server.RefreshTokenSecret, l.Env.Server.RefreshTokenExpiry)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err,
})
return
}
refreshTokenData := domain.RefreshTokenData{
Username: data.Username,
RefreshToken: refreshToken,
}

err = l.RefreshTokenUseCase.StoreRefreshToken(gctx, refreshTokenData)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"error": "Internal Server Error",
"cause": err.Error(),
})
return
}

} else {
gctx.Request.Method = "GET"
gctx.Writer.Header().Set("Authorization", "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6Im5qaXIgICAgICAgICAgICAgICAgIiwiZXhwIjoxNzA5MzAwNjAxfQ.spYgQzKUSq5V7soS2aAXrF7qx-gCCJKLZ-9WN8JDjLY")
gctx.Redirect(http.StatusSeeOther, "/user")
gctx.Next()
return
}

resp := &domain.LoginResponse{
AccessToken: accessToken,
RefreshToken: refreshToken,
}
gctx.JSON(http.StatusOK, resp)
}
4 changes: 2 additions & 2 deletions domain/login_domain.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,12 @@ type JWTClaims struct {
}

type LoginResponse struct {
AccessToken string
RefreshToken string
AccessToken string
}

type LoginUseCase interface {
CheckUser(c context.Context, usecase string) (*User, error)
CreateAccessToken(user *User, secret string, expire int) (string, error)
CreateRefreshToken(user *User, secret string, expire int) (string, error)
GetUsernameFromClaim(user string, secret string) (string, error)
}
22 changes: 22 additions & 0 deletions domain/refresh_token_domain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package domain

import "context"

type RefreshTokenData struct {
Username string
RefreshToken string
}

type RefreshTokenRepository interface {
StoreRefreshToken(context.Context, RefreshTokenData) (err error)
GetRefreshToken(context.Context, string) (*RefreshTokenData, error)
// UpdateRefreshToken(context.Context, string) (string, error)
// DeleteRefreshToken(context.Context, string) (bool, error)
}

type RefreshTokenUseCase interface {
StoreRefreshToken(context.Context, RefreshTokenData) (err error)
GetRefreshToken(context.Context, string) (*RefreshTokenData, error)
// UpdateRefreshToken(context.Context, string) (string, error)
// DeleteRefreshToken(context.Context, string) (bool, error)
}
9 changes: 4 additions & 5 deletions middleware/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,8 @@ func JWTMiddleware(tokenSecret string) gin.HandlerFunc {
ok, err := config.IsAuthorized(authToken, tokenSecret)
if err != nil {
gctx.JSON(http.StatusInternalServerError, gin.H{
"Error": "Internal Status Error",
"middleware": "JWTMiddleware",
"Cause": err.Error(),
"Error": "Internal Status Error",
"Cause": err.Error(),
})
gctx.Abort()
return
Expand All @@ -60,14 +59,14 @@ func JWTMiddleware(tokenSecret string) gin.HandlerFunc {
gctx.Abort()
return
}
gctx.Next()
return
} else {
gctx.JSON(http.StatusUnauthorized, gin.H{
"error": "Unauthorized",
})
gctx.Abort()
return
}
gctx.Next()
return
}
}
29 changes: 23 additions & 6 deletions migrations/user.sql
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
CREATE TABLE IF NOT EXISTS "User" (
username CHAR(20) NOT NULL PRIMARY KEY,
full_name VARCHAR(50) NOT NULL,
email VARCHAR(50) NOT NULL,
password VARCHAR(16) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
username CHAR(20) NOT NULL PRIMARY KEY,
full_name VARCHAR(50) NOT NULL,
email VARCHAR(50) NOT NULL,
password VARCHAR(16) NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL
);


CREATE TABLE IF NOT EXISTS "refresh_token" (
id_refresh SERIAL PRIMARY KEY,
username CHAR(20) NOT NULL,
refresh_token TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP NOT NULL,
FOREIGN KEY (username) REFERENCES "User"(username) ON UPDATE CASCADE ON DELETE CASCADE
);
CREATE INDEX refresh_token_index ON refresh_token(username)

CREATE OR REPLACE FUNCTION updated_auto_func() RETURNS TRIGGER
LANGUAGE 'plpgsql'
AS $$
Expand All @@ -24,6 +35,12 @@ CREATE OR REPLACE TRIGGER updated_auto
FOR EACH ROW
EXECUTE PROCEDURE updated_auto_func();

CREATE OR REPLACE TRIGGER updated_auto
BEFORE UPDATE
ON
"refresh_token"
FOR EACH ROW
EXECUTE PROCEDURE updated_auto_func();

INSERT INTO "User" (username,full_name,email,password) VALUES
('njir','njirlah coeg','njircoeg@tahoo.com','thispassword'),
Expand Down
70 changes: 70 additions & 0 deletions repository/postgres/refresh_token_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package postgres

import (
"context"
"database/sql"
"log"

"github.com/jrione/gin-crud/domain"
)

type postgreRefreshTokenRepository struct {
DBClient *sql.DB
}

func NewRefreshTokenRepository(conn *sql.DB) domain.RefreshTokenRepository {
return &postgreRefreshTokenRepository{
DBClient: conn,
}
}

func (p *postgreRefreshTokenRepository) StoreRefreshToken(ctx context.Context, data domain.RefreshTokenData) (err error) {
query := `INSERT INTO refresh_token(username,refresh_token) VALUES( $1, $2 )`
state, err := p.DBClient.PrepareContext(ctx, query)
if err != nil {
log.Printf("Error PrepareContext: %s", err)
return err
}
defer state.Close()
_, err = state.ExecContext(ctx, data.Username, data.RefreshToken)
if err != nil {
log.Printf("Error Exec: %s", err)
return err
}
return nil
}

func (p *postgreRefreshTokenRepository) GetRefreshToken(ctx context.Context, us string) (res *domain.RefreshTokenData, err error) {
query := `SELECT username,refresh_token FROM refresh_token WHERE username=$1`
state, err := p.DBClient.PrepareContext(ctx, query)
log.Print(query)
if err != nil {
log.Printf("Error PrepareContext: %s", err)
return nil, err
}
defer state.Close()
row := state.QueryRow(us)

res = &domain.RefreshTokenData{}
err = row.Scan(
&res.Username,
&res.RefreshToken,
)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
log.Print(err)
return nil, err
}
return res, nil

}

// func (p *postgreRefreshTokenRepository) UpdateRefreshToken(ctx context.Context, refreshToken string) (string, error) {
// return "", nil
// }

// func (p *postgreRefreshTokenRepository) DeleteRefreshToken(ctx context.Context, refreshToken string) (ok bool, err error) {
// return false, nil
// }
Loading

0 comments on commit 49ac448

Please sign in to comment.