update database.go, go.mod, go.sum, google_oauth.go, Makefile, styles.css, telegram_oauth.go

This commit is contained in:
redtea committed 2024-10-21 12:00:00 +03:00
1 parent c05aa6b2f8
commit d24b3837f1
7 files changed
+731

No files matched your search

+115
View File
@@ -0,0 +1,115 @@
package oauth
import (
"context"
"encoding/json"
"esauth/pkg/repository"
"esauth/pkg/service"
"esauth/pkg/util"
"fmt"
"net/http"
"strings"
"github.com/gin-gonic/gin"
"golang.org/x/oauth2"
)
type GoogleOAuth struct {
Service *service.Service
Repo *repository.Repository
GoogleOauthConfig *oauth2.Config
}
const GOOGLE_RANDOM_STATE = "xeiojqj433DS" // Строка состояния для защиты от CSRF
// Обработка колбэка с авторизацией
func (g GoogleOAuth) HandleGoogleCallback(c *gin.Context) {
// Проверяем состояние (state) для защиты от CSRF атак
if c.Query("state") != GOOGLE_RANDOM_STATE {
util.NewErrorResponse(c, http.StatusInternalServerError, "Invalid OAuth state")
return
}
// Получаем код авторизации от Google
code := c.Query("code")
if code == "" {
util.NewErrorResponse(c, http.StatusInternalServerError, "Code not found")
return
}
// Обмен кода авторизации на токен
token, err := g.GoogleOauthConfig.Exchange(context.Background(), code)
if err != nil {
util.NewErrorResponse(c, http.StatusInternalServerError, "Failed to exchange token")
return
}
// Получаем информацию о пользователе с помощью токена
userInfo, err := getUserInfo(token.AccessToken)
if err != nil {
util.NewErrorResponse(c, http.StatusInternalServerError, "Failed to get user info")
return
}
id, _ := userInfo["id"].(string)
user, _ := g.Repo.FindUser("google_id = ?", id)
usertoken := c.Query("usertoken")
currentUserId, err := g.Service.ParseToken(usertoken)
if err == nil {
currentUser, _ := g.Repo.GetUser(currentUserId)
if (user.Id != currentUser.Id) && (user.Id != 0) {
util.NewErrorResponse(c, http.StatusInternalServerError, fmt.Sprintf("This account already conntected to user %d", user.Id))
return
}
user = currentUser
}
if user.Username == "" {
username, _ := userInfo["name"].(string)
user.Username = strings.ReplaceAll(username, " ", "")
}
if user.FirstName == "" {
user.FirstName, _ = userInfo["given_name"].(string)
}
if user.LastName == "" {
user.LastName, _ = userInfo["family_name"].(string)
}
if user.PictureURL == "" {
user.PictureURL, _ = userInfo["picture"].(string)
}
if user.Email == "" {
user.Email, _ = userInfo["email"].(string)
}
user.GoogleId = id
_, err = g.Service.CreateUser(&user)
if err != nil {
g.Service.SaveUser(&user)
}
tkn, err := g.Service.GenerateToken(user.Id)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.Redirect(http.StatusMovedPermanently, "/?token="+tkn)
c.Abort()
}
// getUserInfo получает информацию о пользователе через Google API
func getUserInfo(accessToken string) (map[string]interface{}, error) {
resp, err := http.Get(fmt.Sprintf("https://www.googleapis.com/oauth2/v2/userinfo?access_token=%s", accessToken))
if err != nil {
return nil, err
}
defer resp.Body.Close()
var userInfo map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&userInfo); err != nil {
return nil, err
}
return userInfo, nil
}
+116
View File
@@ -0,0 +1,116 @@
package oauth
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"esauth/pkg/repository"
"esauth/pkg/service"
"esauth/pkg/util"
"fmt"
"net/http"
"sort"
"strconv"
"strings"
"time"
"github.com/gin-gonic/gin"
)
type TelegramOAuth struct {
Service *service.Service
Repo *repository.Repository
BotId string
BotToken string
}
func (t TelegramOAuth) HandleTelegramOAuthCallback(c *gin.Context) {
// Получаем хеш и параметры из запроса.
hash := c.Query("hash")
// Собираем параметры для формирования data-check-string.
params := map[string]string{
"id": c.Query("id"),
"first_name": c.Query("first_name"),
"last_name": c.Query("last_name"),
"username": c.Query("username"),
"auth_date": c.Query("auth_date"),
"photo_url": c.Query("photo_url"),
}
// Удаляем пустые значения из параметров.
var validParams []string
for key, value := range params {
if value != "" {
validParams = append(validParams, fmt.Sprintf("%s=%s", key, value))
}
}
// Сортируем параметры в алфавитном порядке.
sort.Strings(validParams)
dataCheckString := strings.Join(validParams, "\n")
// Генерируем секретный ключ через SHA256 от botToken.
secretKey := sha256.Sum256([]byte(t.BotToken))
// Вычисляем HMAC-SHA256 от data-check-string с использованием secretKey.
h := hmac.New(sha256.New, secretKey[:])
h.Write([]byte(dataCheckString))
calculatedHash := hex.EncodeToString(h.Sum(nil))
// Сравниваем вычисленный хеш с переданным.
if calculatedHash != hash {
util.NewErrorResponse(c, http.StatusBadRequest, "Invalid hash")
return
}
// Проверка времени auth_date, чтобы избежать устаревших данных.
date, _ := strconv.ParseInt(params["auth_date"], 10, 64)
if time.Now().Unix()-int64(date) > 86400 {
util.NewErrorResponse(c, http.StatusUnauthorized, "Authorization request expired")
return
}
user, _ := t.Repo.FindUser("telegram_id = ?", params["id"])
usertoken := c.Query("usertoken")
currentUserId, err := t.Service.ParseToken(usertoken)
if err == nil {
currentUser, _ := t.Repo.GetUser(currentUserId)
if (user.Id != currentUser.Id) && (user.Id != 0) {
util.NewErrorResponse(c, http.StatusUnauthorized, fmt.Sprintf("This account already conntected to user %d", user.Id))
return
}
user = currentUser
}
if user.Username == "" {
user.Username = params["username"]
}
if user.FirstName == "" {
user.FirstName = params["first_name"]
}
if user.LastName == "" {
user.LastName = params["last_name"]
}
if user.PictureURL == "" {
user.PictureURL = params["photo_url"]
}
if user.Email == "" {
user.Email = ""
}
user.TelegramId = params["id"]
_, err = t.Service.CreateUser(&user)
if err != nil {
t.Service.SaveUser(&user)
}
tkn, err := t.Service.GenerateToken(user.Id)
if err != nil {
util.NewErrorResponse(c, http.StatusUnauthorized, err.Error())
return
}
c.Redirect(http.StatusMovedPermanently, "/?token="+tkn)
c.Abort()
}
+41
View File
@@ -0,0 +1,41 @@
package repository
import (
"esauth"
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type Config struct {
Host string
Port string
Username string
Password string
DBName string
}
func NewDB(cfg Config) (*gorm.DB, error) {
dsn := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local",
cfg.Username,
cfg.Password,
cfg.Host,
cfg.Port,
cfg.DBName)
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
Logger: logger.Default.LogMode(logger.Silent),
})
if err != nil {
return nil, err
}
err = db.Set("gorm:table_options", "CHARSET=utf8mb4").AutoMigrate(&esauth.User{})
if err != nil {
return nil, err
}
return db, nil
}