first commit
This commit is contained in:
71
services/auth/auth.routes.go
Normal file
71
services/auth/auth.routes.go
Normal file
@@ -0,0 +1,71 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
// Login login using westone account
|
||||
//
|
||||
// @Summary login using westone account
|
||||
// @Description login using westone account
|
||||
// @Tags Auth
|
||||
// @Accept json
|
||||
// @Param parameter body types.SignInPayload true "parameter"
|
||||
// @Produce json
|
||||
// @Success 200 {object} map[string]any
|
||||
// @Failure 400 {object} map[string]any
|
||||
// @Failure 500 {object} map[string]any
|
||||
// @Router /westone/api/v1/auth/login [post]
|
||||
func (h *Handler) handleLogin(w http.ResponseWriter, r *http.Request) {
|
||||
var payload types.SignInPayload
|
||||
fmt.Println("User Agent")
|
||||
fmt.Println(r.UserAgent())
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
h.store.LogSignIn(payload.Email, r.RemoteAddr, "FAILED", "LOGIN", "westone")
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
errorz := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errorz))
|
||||
h.store.LogSignIn(payload.Email, r.RemoteAddr, "FAILED", "LOGIN", "westone")
|
||||
return
|
||||
}
|
||||
|
||||
hashedPassword := HashWithMD5(payload.Password)
|
||||
response, err := h.store.SignInWestone(payload.Email, hashedPassword)
|
||||
if err != nil {
|
||||
var logError *utils.LogError
|
||||
if errors.As(err, &logError) {
|
||||
h.errorz.CreateErrorLog(*logError)
|
||||
utils.WriteErrorLog(w, http.StatusBadRequest, *logError)
|
||||
}
|
||||
h.store.LogSignIn(payload.Email, r.RemoteAddr, "FAILED", "LOGIN", "westone")
|
||||
return
|
||||
}
|
||||
|
||||
timenow := time.Now()
|
||||
token, err := CreateJWT(types.DataJWT{
|
||||
User: *response,
|
||||
Ip: r.RemoteAddr,
|
||||
Agent: r.UserAgent(),
|
||||
Version: "v1",
|
||||
LastLogin: timenow.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
h.store.LogSignIn(payload.Email, r.RemoteAddr, "FAILED", "LOGIN", "westone")
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSONLogin(w, http.StatusOK, response, token, "westone")
|
||||
}
|
||||
77
services/auth/auth.store.go
Normal file
77
services/auth/auth.store.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
func (s *Store) SignInWestone(email string, password string) (*types.User, error) {
|
||||
user := new(types.User)
|
||||
|
||||
qry := `
|
||||
SELECT
|
||||
M_UserID,
|
||||
M_UserName,
|
||||
M_UserGroupDashboard,
|
||||
IFNULL(M_UserFullName, "") as M_StaffName,
|
||||
10000000 as time_autologout
|
||||
FROM m_user
|
||||
JOIN m_usergroup ON M_UserM_UserGroupID = M_UserGroupID
|
||||
LEFT JOIN m_staff on M_UserM_StaffID = M_StaffID
|
||||
WHERE M_UserName = ? AND M_UserPassword = ? AND M_UserIsActive = 'Y'
|
||||
`
|
||||
if err := s.db.Get(user, qry, email, password); err != nil {
|
||||
return nil, &utils.LogError{
|
||||
Code: "151",
|
||||
TraceID: utils.RandomTraceID(15),
|
||||
Type: "AUTH",
|
||||
Params: "email: " + email,
|
||||
Query: "-",
|
||||
Message: err.Error(),
|
||||
TimeStamp: time.Now().Format("2006-01-02 15:04:05"),
|
||||
}
|
||||
}
|
||||
user.Type_Akun = "westone"
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (s *Store) LogSignIn(email string, ip string, status string, tipe string, provider string) error {
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
logval := types.LogLogin{
|
||||
LogLoginMUserEmail: email,
|
||||
LogLoginDateTime: time.Now().Format("2006-01-02 15:04:05"),
|
||||
LogLoginIP: ip,
|
||||
LogLoginType: tipe,
|
||||
LogLoginStatus: status,
|
||||
LogLoginLogin: provider,
|
||||
}
|
||||
|
||||
qry := `INSERT INTO log_login
|
||||
(Log_LoginM_UserEmail, Log_LoginDateTime, Log_LoginIP, Log_LoginType, Log_LoginStatus, Log_LoginLogin)
|
||||
VALUES (:Log_LoginM_UserEmail, :Log_LoginDateTime, :Log_LoginIP, :Log_LoginType, :Log_LoginStatus, :Log_LoginLogin)`
|
||||
|
||||
_, err = tx.NamedExec(qry, logval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
134
services/auth/jwt.go
Normal file
134
services/auth/jwt.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"sismedika.com/sas/westone/configs"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type ContextKey string
|
||||
|
||||
const UserContextKey ContextKey = "mk1S2sKM12KASd02dp1"
|
||||
|
||||
// func WithJWTAuth(handlerFunc http.HandlerFunc, store types.UserStore) http.HandlerFunc {
|
||||
// return func(w http.ResponseWriter, r *http.Request) {
|
||||
// tokenString := utils.GetTokenFromRequest(r)
|
||||
// token, err := validateJWT(tokenString)
|
||||
// if err != nil {
|
||||
// log.Printf("failed to validate token: %v", err)
|
||||
// permissionDenied(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// if !token.Valid {
|
||||
// log.Println("invalid token")
|
||||
// permissionDenied(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// claims := token.Claims.(jwt.MapClaims)
|
||||
// str := claims["userID"].(string)
|
||||
|
||||
// userID, err := strconv.Atoi(str)
|
||||
// if err != nil {
|
||||
// log.Printf("failed to convert userID to int: %v", err)
|
||||
// permissionDenied(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// u, err := store.GetUserByID(userID)
|
||||
// if err != nil {
|
||||
// log.Printf("failed to get user by id: %v", err)
|
||||
// permissionDenied(w)
|
||||
// return
|
||||
// }
|
||||
|
||||
// // Add the user to the context
|
||||
// ctx := r.Context()
|
||||
// ctx = context.WithValue(ctx, UserKey, u.MUserID)
|
||||
// r = r.WithContext(ctx)
|
||||
|
||||
// // Call the function if the token is valid
|
||||
// handlerFunc(w, r)
|
||||
// }
|
||||
// }
|
||||
|
||||
func CreateJWT(data types.DataJWT) (string, error) {
|
||||
secret := []byte(configs.Envs.JWTSecret)
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"M_UserID": data.M_UserID,
|
||||
"M_UserEmail": data.M_UserEmail,
|
||||
"M_UserUsername": data.M_UserUsername,
|
||||
"M_UserGroupDashboard": data.M_UserGroupDashboard,
|
||||
"M_UserDefaultTSampleStationID": data.M_UserDefaultTSampleStationID,
|
||||
"M_StaffName": data.M_StaffName,
|
||||
"Is_Courier": data.Is_Courier,
|
||||
"Time_Autologout": data.Time_Autologout,
|
||||
"Type_Akun": data.Type_Akun,
|
||||
"IP": data.Ip,
|
||||
"Agent": data.Agent,
|
||||
"Version": data.Version,
|
||||
"LastLogin": data.LastLogin,
|
||||
})
|
||||
|
||||
tokenString, err := token.SignedString(secret)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return tokenString, err
|
||||
}
|
||||
|
||||
func validateJWT(tokenString string) (*jwt.Token, error) {
|
||||
return jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
|
||||
}
|
||||
|
||||
return []byte(configs.Envs.JWTSecret), nil
|
||||
})
|
||||
}
|
||||
|
||||
func permissionDenied(w http.ResponseWriter) {
|
||||
utils.WriteError(w, http.StatusForbidden, fmt.Errorf("PERMISSION DENIED"))
|
||||
}
|
||||
|
||||
func GetUserIDFromContext(ctx context.Context) int {
|
||||
userID, ok := ctx.Value(UserContextKey).(int)
|
||||
if !ok {
|
||||
return -1
|
||||
}
|
||||
|
||||
return userID
|
||||
}
|
||||
|
||||
func AuthMiddleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tokenstr := utils.GetTokenFromRequest(r)
|
||||
token, err := validateJWT(tokenstr)
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] Failed to validate jwt token: %v", err)
|
||||
permissionDenied(w)
|
||||
return
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
log.Println("[ERROR] Invalid token")
|
||||
permissionDenied(w)
|
||||
return
|
||||
}
|
||||
|
||||
claims := token.Claims.(jwt.MapClaims)
|
||||
ctx := r.Context()
|
||||
ctx = context.WithValue(ctx, UserContextKey, claims)
|
||||
r = r.WithContext(ctx)
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
18
services/auth/jwt_test.go
Normal file
18
services/auth/jwt_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"sismedika.com/sas/westone/types"
|
||||
)
|
||||
|
||||
func TestCreateJWT(t *testing.T) {
|
||||
token, err := CreateJWT(types.DataJWT{})
|
||||
if err != nil {
|
||||
t.Errorf("error creating JWT: %v", err)
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
t.Error("expected token to be not empty")
|
||||
}
|
||||
}
|
||||
73
services/auth/oauth.go
Normal file
73
services/auth/oauth.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/gothic"
|
||||
"github.com/markbates/goth/providers/google"
|
||||
"sismedika.com/sas/westone/configs"
|
||||
)
|
||||
|
||||
var maxAge = 86400 * 1 // 30 days
|
||||
var isProd = false // set to true if in production
|
||||
var key = configs.Envs.SessionKey
|
||||
var store = sessions.NewCookieStore([]byte(key))
|
||||
var usersession = "user"
|
||||
|
||||
func NewOAuth() {
|
||||
googleClientID := configs.Envs.GoogleClientID
|
||||
googleClientSecret := configs.Envs.GoogleClientSecret
|
||||
callback := configs.Envs.PublicHost + ":" + configs.Envs.Port + "/api/v1/auth/google/callback"
|
||||
|
||||
store.MaxAge(maxAge)
|
||||
store.Options.Path = "/"
|
||||
store.Options.HttpOnly = true
|
||||
store.Options.Secure = isProd
|
||||
store.Options.SameSite = http.SameSiteLaxMode
|
||||
|
||||
gothic.Store = store
|
||||
|
||||
goth.UseProviders(
|
||||
google.New(googleClientID, googleClientSecret, callback),
|
||||
)
|
||||
}
|
||||
|
||||
func addUsertoSession(w http.ResponseWriter, r *http.Request, user goth.User) error {
|
||||
user_session, err := store.Get(r, usersession)
|
||||
if err != nil {
|
||||
user_session, err = store.New(r, usersession)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] get user to session, error: ", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
user.RawData = map[string]interface{}{}
|
||||
user_session.Values["user"] = user
|
||||
err = user_session.Save(r, w)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] saving user to session, error: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func removeUsertoSession(w http.ResponseWriter, r *http.Request) error {
|
||||
user_session, err := store.Get(r, usersession)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] get user to session, error: ", err)
|
||||
return err
|
||||
}
|
||||
user_session.Values["user"] = goth.User{}
|
||||
err = user_session.Save(r, w)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] remove user to session, error: ", err)
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
249
services/auth/oauth.routes.go
Normal file
249
services/auth/oauth.routes.go
Normal file
@@ -0,0 +1,249 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/gorilla/mux"
|
||||
"github.com/markbates/goth"
|
||||
"github.com/markbates/goth/gothic"
|
||||
"sismedika.com/sas/westone/configs"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
store types.OauthStore
|
||||
errorz types.ErrorLogStore
|
||||
usrStore types.UserStore
|
||||
}
|
||||
|
||||
func NewHandler(store types.OauthStore, errorz types.ErrorLogStore, usrStore types.UserStore) *Handler {
|
||||
return &Handler{
|
||||
store: store,
|
||||
errorz: errorz,
|
||||
usrStore: usrStore,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) RegisterRoutes(router *mux.Router) {
|
||||
authroute := router.PathPrefix("/auth").Subrouter()
|
||||
|
||||
authroute.HandleFunc("/generateauthcode", h.handlerGenerateAuthCode).Methods(http.MethodPost)
|
||||
|
||||
// auth
|
||||
authroute.HandleFunc("/login", h.handleLogin).Methods(http.MethodPost)
|
||||
// oauth
|
||||
authroute.HandleFunc("/{provider}/login", h.handleAuthLogin).Methods(http.MethodGet)
|
||||
authroute.HandleFunc("/{provider}/logout", h.handleAuthLogout).Methods(http.MethodGet)
|
||||
authroute.HandleFunc("/{provider}/callback", h.handleAuthCallback).Methods(http.MethodGet)
|
||||
authroute.HandleFunc("/{provider}/linking", h.handleAuthLinkAccount).Methods(http.MethodPost)
|
||||
authroute.HandleFunc("/{provider}/authcode", h.handlerRedirectCode).Methods(http.MethodGet)
|
||||
|
||||
protec := authroute.PathPrefix("/redirect").Subrouter()
|
||||
protec.Use(AuthMiddleware)
|
||||
protec.HandleFunc("/dashboard", h.handleRedirectDash).Methods(http.MethodGet)
|
||||
}
|
||||
|
||||
func (h *Handler) handleAuthLogin(w http.ResponseWriter, r *http.Request) {
|
||||
if gothUser, err := gothic.CompleteUserAuth(w, r); err == nil {
|
||||
// log.Println(gothUser)
|
||||
log.Printf("[INFO] account found: %v", gothUser.Email)
|
||||
} else {
|
||||
gothic.BeginAuthHandler(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleAuthCallback(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
provider := vars["provider"]
|
||||
|
||||
akungoogle, err := gothic.CompleteUserAuth(w, r)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// add user to cookie session
|
||||
err = addUsertoSession(w, r, akungoogle)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// check google is linked to server
|
||||
userID, err := h.store.CheckGoogleAccountLinked(types.UserGoogle{M_UserGoogleEmail: akungoogle.Email, M_UserGoogleIdentifier: akungoogle.UserID})
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
if userID > 0 {
|
||||
// generate jwt token
|
||||
user, err := h.usrStore.GetUserByID(userID)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
user.Type_Akun = provider
|
||||
timenow := time.Now()
|
||||
token, err := CreateJWT(types.DataJWT{
|
||||
User: *user,
|
||||
Ip: r.RemoteAddr,
|
||||
Agent: r.UserAgent(),
|
||||
Version: "v1",
|
||||
LastLogin: timenow.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
// data := "Bearer " + token
|
||||
// w.Header().Set("Authorization", data)
|
||||
// http.Redirect(w, r, "/api/v1/auth/redirect/dashboard", http.StatusFound)
|
||||
utils.WriteJSONLogin(w, http.StatusOK, user, token, provider)
|
||||
} else {
|
||||
log.Println("[INFO] redirect to auth-code")
|
||||
// http.Redirect(w, r, "/api/v1/auth/"+provider+"/authcode", http.StatusFound)
|
||||
http.Redirect(w, r, configs.Envs.PublicHost+"/auth-code", http.StatusFound)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) handleRedirectDash(w http.ResponseWriter, r *http.Request) {
|
||||
claims, ok := r.Context().Value(UserContextKey).(jwt.MapClaims)
|
||||
if !ok {
|
||||
utils.WriteError(w, http.StatusInternalServerError, fmt.Errorf("token not found in context"))
|
||||
return
|
||||
}
|
||||
|
||||
username := claims["Username"].(string)
|
||||
temp := "WELCOME TO DASHBOARD, " + username
|
||||
utils.WriteJSON(w, http.StatusOK, temp)
|
||||
}
|
||||
|
||||
func (h *Handler) handlerRedirectCode(w http.ResponseWriter, r *http.Request) {
|
||||
user_session, _ := store.Get(r, usersession)
|
||||
user := user_session.Values["user"].(goth.User)
|
||||
if user.IDToken == "" {
|
||||
log.Println("[ERROR] token not found in context")
|
||||
utils.WriteJSON(w, http.StatusInternalServerError, "not found")
|
||||
return
|
||||
}
|
||||
|
||||
temp := "INSERT AUTH CODE, " + user.Email
|
||||
utils.WriteJSON(w, http.StatusOK, temp)
|
||||
}
|
||||
|
||||
func (h *Handler) handleAuthLogout(w http.ResponseWriter, r *http.Request) {
|
||||
err := gothic.Logout(w, r)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
err = removeUsertoSession(w, r)
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSON(w, http.StatusOK, "success logout")
|
||||
}
|
||||
|
||||
func (h *Handler) handlerGenerateAuthCode(w http.ResponseWriter, r *http.Request) {
|
||||
var payload types.GenerateAuthCode
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
errorss := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errorss))
|
||||
return
|
||||
}
|
||||
|
||||
err := h.store.GenerateAuthCode(payload.Email, payload.Types, payload.UserID)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] failed generate auth code")
|
||||
utils.WriteJSON(w, http.StatusInternalServerError, "failed generate auth code")
|
||||
return
|
||||
}
|
||||
|
||||
msg := "Auth Code Generated for " + payload.Email
|
||||
utils.WriteJSON(w, http.StatusOK, msg)
|
||||
}
|
||||
|
||||
func (h *Handler) handleAuthLinkAccount(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
provider := vars["provider"]
|
||||
|
||||
// var payload types.AuthCode
|
||||
var payload types.AuthCodePayload
|
||||
if err := utils.ParseJSON(r, &payload); err != nil {
|
||||
utils.WriteError(w, http.StatusBadRequest, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := utils.Validate.Struct(payload); err != nil {
|
||||
errors := err.(validator.ValidationErrors)
|
||||
utils.WriteError(w, http.StatusBadRequest, fmt.Errorf("invalid payload: %v", errors))
|
||||
return
|
||||
}
|
||||
|
||||
user_session, _ := store.Get(r, usersession)
|
||||
akunGoogle := user_session.Values["user"].(goth.User)
|
||||
if akunGoogle.IDToken == "" {
|
||||
log.Println("[ERROR] google token not found in context")
|
||||
utils.WriteError(w, http.StatusInternalServerError, fmt.Errorf("token not found"))
|
||||
return
|
||||
}
|
||||
|
||||
userID, err := h.store.CompareAuthCode(payload.Code, akunGoogle, provider)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] compare auth code")
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
user, err := h.usrStore.GetUserByID(userID)
|
||||
if err != nil {
|
||||
log.Println("[ERROR] cant get m_user data")
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
user.Type_Akun = provider
|
||||
timenow := time.Now()
|
||||
token, err := CreateJWT(types.DataJWT{
|
||||
User: *user,
|
||||
Ip: r.RemoteAddr,
|
||||
Agent: r.UserAgent(),
|
||||
Version: "v1",
|
||||
LastLogin: timenow.Format("2006-01-02 15:04:05"),
|
||||
})
|
||||
if err != nil {
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = h.store.AddGoolgeAccount(types.UserGoogle{
|
||||
M_UserGoogleM_UserID: user.M_UserID,
|
||||
M_UserGoogleEmail: akunGoogle.Email,
|
||||
M_UserGoogleIdentifier: akunGoogle.UserID,
|
||||
M_UserGoogleCode: payload.Code,
|
||||
M_UserGoogleToken: token,
|
||||
})
|
||||
if err != nil {
|
||||
log.Printf("[ERROR] failed insert google account to db, %v", err)
|
||||
utils.WriteError(w, http.StatusInternalServerError, err)
|
||||
return
|
||||
}
|
||||
|
||||
utils.WriteJSONLogin(w, http.StatusOK, user, token, provider)
|
||||
}
|
||||
143
services/auth/oauth.store.go
Normal file
143
services/auth/oauth.store.go
Normal file
@@ -0,0 +1,143 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/markbates/goth"
|
||||
"sismedika.com/sas/westone/types"
|
||||
"sismedika.com/sas/westone/utils"
|
||||
)
|
||||
|
||||
type Store struct {
|
||||
db *sqlx.DB
|
||||
}
|
||||
|
||||
func NewStore(db *sqlx.DB) *Store {
|
||||
return &Store{db: db}
|
||||
}
|
||||
|
||||
func (s *Store) AddGoolgeAccount(user types.UserGoogle) error {
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
query := `INSERT INTO m_usergoogle (
|
||||
M_UserGoogleM_UserID,
|
||||
M_UserGoogleEmail,
|
||||
M_UserGoogleIdentifier,
|
||||
M_UserGoogleCode,
|
||||
M_UserGoogleToken
|
||||
) VALUES (:M_UserGoogleM_UserID, :M_UserGoogleEmail, :M_UserGoogleIdentifier, :M_UserGoogleCode, :M_UserGoogleToken)`
|
||||
_, err = tx.NamedExec(query, user)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CheckGoogleAccountLinked(user types.UserGoogle) (int, error) {
|
||||
var userID int
|
||||
|
||||
qry := `SELECT
|
||||
M_UserGoogleM_UserID
|
||||
FROM m_usergoogle
|
||||
WHERE M_UserGoogleEmail = ? AND M_UserGoogleIdentifier = ?`
|
||||
if err := s.db.Get(&userID, qry, user.M_UserGoogleEmail, user.M_UserGoogleIdentifier); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if userID < 1 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return userID, nil
|
||||
}
|
||||
|
||||
func (s *Store) GenerateAuthCode(email string, jenis string, userid int) error {
|
||||
payload := types.AuthCode{
|
||||
AuthCodeMUserID: userid,
|
||||
AuthCodeUser: email,
|
||||
AuthCodeType: jenis,
|
||||
AuthCodeCode: utils.RandomTraceID(6),
|
||||
}
|
||||
|
||||
tx, err := s.db.BeginTxx(context.Background(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
}
|
||||
}()
|
||||
|
||||
sql := `INSERT INTO x_auth_code (
|
||||
AuthCodeMUserID,
|
||||
AuthCodeUser,
|
||||
AuthCodeType,
|
||||
AuthCodeCode
|
||||
) VALUES (:AuthCodeMUserID, :AuthCodeUser, :AuthCodeType, :AuthCodeCode)`
|
||||
_, err = tx.NamedExec(sql, payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) CompareAuthCode(authcode string, user goth.User, typez string) (int, error) {
|
||||
var code types.AuthCode
|
||||
|
||||
qry := `SELECT
|
||||
AuthCodeID,
|
||||
AuthCodeMUserID,
|
||||
AuthCodeUser,
|
||||
AuthCodeType,
|
||||
AuthCodeCode,
|
||||
AuthCodeIsUsed
|
||||
FROM x_auth_code
|
||||
WHERE AuthCodeUser = ? AND AuthCodeType = ?
|
||||
AND AuthCodeIsUsed = 'N' AND AuthCodeIsActive = 'Y'`
|
||||
if err := s.db.Get(&code, qry, user.Email, typez); err != nil {
|
||||
return 0, fmt.Errorf("auth code not found, %v", err)
|
||||
}
|
||||
|
||||
if authcode != code.AuthCodeCode {
|
||||
return 0, fmt.Errorf("auth code do not match")
|
||||
} else {
|
||||
inst := `
|
||||
UPDATE x_auth_code
|
||||
SET AuthCodeIsUsed = 'Y'
|
||||
WHERE AuthCodeUser = ? AND AuthCodeType = ? AND AuthCodeCode = ?
|
||||
`
|
||||
if _, err := s.db.Exec(inst, user.Email, typez, authcode); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return code.AuthCodeMUserID, nil
|
||||
}
|
||||
}
|
||||
29
services/auth/password.go
Normal file
29
services/auth/password.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/hex"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func HashPassword(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(hash), nil
|
||||
}
|
||||
|
||||
func ComparePasswords(hashed string, plain []byte) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(hashed), plain)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func HashWithMD5(password string) string {
|
||||
salt := "545"
|
||||
data := []byte(salt + password + salt)
|
||||
hash := md5.Sum(data)
|
||||
return hex.EncodeToString(hash[:])
|
||||
}
|
||||
34
services/auth/password_test.go
Normal file
34
services/auth/password_test.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestHashPassword(t *testing.T) {
|
||||
hash, err := HashPassword("password")
|
||||
if err != nil {
|
||||
t.Errorf("error hashing password: %v", err)
|
||||
}
|
||||
|
||||
if hash == "" {
|
||||
t.Error("expected hash to be not empty")
|
||||
}
|
||||
|
||||
if hash == "password" {
|
||||
t.Error("expected hash to be different from password")
|
||||
}
|
||||
}
|
||||
|
||||
func TestComparePasswords(t *testing.T) {
|
||||
hash, err := HashPassword("password")
|
||||
if err != nil {
|
||||
t.Errorf("error hashing password: %v", err)
|
||||
}
|
||||
|
||||
if !ComparePasswords(hash, []byte("password")) {
|
||||
t.Errorf("expected password to match hash")
|
||||
}
|
||||
if ComparePasswords(hash, []byte("notpassword")) {
|
||||
t.Errorf("expected password to not match hash")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user