- Add GetPendingRegistrationsData() to DeviceHandler - Add GetQueueData() to QueueHandler - Add template types: DeviceData, PendingRegistrationData, ConflictData, QueueItemData - Add collection types: CollectionData, CollectionDetailData, BookData All changes are non-breaking and preserve API compatibility
565 lines
18 KiB
Go
565 lines
18 KiB
Go
package handlers
|
|
|
|
import (
|
|
"bookmann/internal/config"
|
|
"bookmann/internal/database"
|
|
"crypto/rand"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/skip2/go-qrcode"
|
|
"golang.org/x/crypto/bcrypt"
|
|
)
|
|
|
|
type DeviceHandler struct {
|
|
db *database.Queries
|
|
jwtKey []byte
|
|
cfg *config.Config
|
|
}
|
|
|
|
func NewDeviceHandler(db *database.Queries, jwtSecret string, cfg *config.Config) *DeviceHandler {
|
|
return &DeviceHandler{
|
|
db: db,
|
|
jwtKey: []byte(jwtSecret),
|
|
cfg: cfg,
|
|
}
|
|
}
|
|
|
|
type DeviceRegistrationRequest struct {
|
|
DeviceName string `json:"device_name" validate:"required,min=1,max=100"`
|
|
DeviceType string `json:"device_type" validate:"required,oneof=koreader kobo web mobile"`
|
|
DeviceIdentifier string `json:"device_identifier" validate:"required,min=1,max=255"`
|
|
}
|
|
|
|
type DeviceRegistrationResponse struct {
|
|
DeviceID uuid.UUID `json:"device_id"`
|
|
RegistrationID string `json:"registration_id"`
|
|
AuthURL string `json:"auth_url"`
|
|
QRCode string `json:"qr_code"`
|
|
ExpiresIn int `json:"expires_in"`
|
|
PollInterval int `json:"poll_interval"`
|
|
SetupInstructions map[string]string `json:"setup_instructions"`
|
|
}
|
|
|
|
type DeviceAuthStatusRequest struct {
|
|
RegistrationID string `json:"registration_id" validate:"required"`
|
|
}
|
|
|
|
type DeviceAuthStatusResponse struct {
|
|
Status string `json:"status"`
|
|
AuthToken string `json:"auth_token,omitempty"`
|
|
DeviceID uuid.UUID `json:"device_id,omitempty"`
|
|
SyncEndpoints map[string]string `json:"sync_endpoints,omitempty"`
|
|
Message string `json:"message,omitempty"`
|
|
ExpiresIn int `json:"expires_in,omitempty"`
|
|
}
|
|
|
|
type DeviceListResponse struct {
|
|
Devices []DeviceInfo `json:"devices"`
|
|
Total int `json:"total"`
|
|
}
|
|
|
|
type DeviceInfo struct {
|
|
ID uuid.UUID `json:"id"`
|
|
DeviceName string `json:"device_name"`
|
|
DeviceType string `json:"device_type"`
|
|
LastSync *time.Time `json:"last_sync"`
|
|
LastSeen *time.Time `json:"last_seen"`
|
|
SyncEnabled bool `json:"sync_enabled"`
|
|
AutoSync bool `json:"auto_sync"`
|
|
SyncFrequency int32 `json:"sync_frequency_minutes"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
DeviceMetadata json.RawMessage `json:"device_metadata,omitempty"`
|
|
}
|
|
|
|
type DeviceUpdateRequest struct {
|
|
DeviceName string `json:"device_name,omitempty" validate:"omitempty,min=1,max=100"`
|
|
SyncEnabled *bool `json:"sync_enabled,omitempty"`
|
|
AutoSync *bool `json:"auto_sync,omitempty"`
|
|
SyncFrequencyMinutes *int32 `json:"sync_frequency_minutes,omitempty" validate:"omitempty,min=1,max=1440"`
|
|
DeviceMetadata json.RawMessage `json:"device_metadata,omitempty"`
|
|
}
|
|
|
|
type DeviceApprovalRequest struct {
|
|
RegistrationID string `json:"registration_id" validate:"required"`
|
|
Approve bool `json:"approve"`
|
|
}
|
|
|
|
type PendingRegistration struct {
|
|
RegistrationID string
|
|
DeviceName string
|
|
DeviceType string
|
|
DeviceIdentifier string
|
|
UserID uuid.UUID
|
|
ExpiresAt time.Time
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
var pendingRegistrations = make(map[string]*PendingRegistration)
|
|
|
|
func (h *DeviceHandler) InitiateRegistration(c echo.Context) error {
|
|
req := DeviceRegistrationRequest{}
|
|
if err := c.Bind(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request format"})
|
|
}
|
|
|
|
if err := c.Validate(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
registrationID := uuid.New().String()
|
|
expiresAt := time.Now().Add(5 * time.Minute)
|
|
|
|
registration := &PendingRegistration{
|
|
RegistrationID: registrationID,
|
|
DeviceName: req.DeviceName,
|
|
DeviceType: req.DeviceType,
|
|
DeviceIdentifier: req.DeviceIdentifier,
|
|
ExpiresAt: expiresAt,
|
|
CreatedAt: time.Now(),
|
|
}
|
|
|
|
pendingRegistrations[registrationID] = registration
|
|
|
|
authURL := fmt.Sprintf("%s/devices/approve/%s", h.cfg.BaseURL, registrationID)
|
|
|
|
qrCode, err := qrcode.Encode(authURL, qrcode.Medium, 256)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate QR code"})
|
|
}
|
|
|
|
qrCodeBase64 := base64.StdEncoding.EncodeToString(qrCode)
|
|
|
|
setupInstructions := map[string]string{}
|
|
switch req.DeviceType {
|
|
case "koreader":
|
|
setupInstructions["koreader"] = fmt.Sprintf("Calibre URL: %s/api/sync/koreader", h.cfg.BaseURL)
|
|
case "kobo":
|
|
setupInstructions["kobo"] = fmt.Sprintf("Sync URL: %s/api/sync/kobo", h.cfg.BaseURL)
|
|
}
|
|
|
|
response := DeviceRegistrationResponse{
|
|
RegistrationID: registrationID,
|
|
AuthURL: authURL,
|
|
QRCode: "data:image/png;base64," + qrCodeBase64,
|
|
ExpiresIn: 300,
|
|
PollInterval: 3,
|
|
SetupInstructions: setupInstructions,
|
|
}
|
|
|
|
return c.JSON(http.StatusCreated, response)
|
|
}
|
|
|
|
func (h *DeviceHandler) CheckRegistrationStatus(c echo.Context) error {
|
|
req := DeviceAuthStatusRequest{}
|
|
if err := c.Bind(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request format"})
|
|
}
|
|
|
|
registration, exists := pendingRegistrations[req.RegistrationID]
|
|
if !exists {
|
|
return c.JSON(http.StatusNotFound, map[string]string{"error": "registration not found"})
|
|
}
|
|
|
|
if time.Now().After(registration.ExpiresAt) {
|
|
delete(pendingRegistrations, req.RegistrationID)
|
|
return c.JSON(http.StatusGone, map[string]string{"error": "registration expired"})
|
|
}
|
|
|
|
if registration.UserID == (uuid.UUID{}) {
|
|
return c.JSON(http.StatusOK, DeviceAuthStatusResponse{
|
|
Status: "pending",
|
|
Message: "awaiting user approval",
|
|
ExpiresIn: int(time.Until(registration.ExpiresAt).Seconds()),
|
|
})
|
|
}
|
|
|
|
authToken, err := generateDeviceToken()
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to generate auth token"})
|
|
}
|
|
|
|
userUUID := registration.UserID
|
|
pgUserID := pgtype.UUID{Bytes: [16]byte(userUUID), Valid: true}
|
|
|
|
syncEnabled := pgtype.Bool{Bool: true, Valid: true}
|
|
autoSync := pgtype.Bool{Bool: true, Valid: true}
|
|
syncFreq := pgtype.Int4{Int32: 5, Valid: true}
|
|
|
|
device, err := h.db.CreateDevice(c.Request().Context(), database.CreateDeviceParams{
|
|
UserID: pgUserID,
|
|
DeviceName: registration.DeviceName,
|
|
DeviceType: registration.DeviceType,
|
|
DeviceIdentifier: registration.DeviceIdentifier,
|
|
AuthToken: authToken,
|
|
SyncEnabled: syncEnabled,
|
|
AutoSync: autoSync,
|
|
SyncFrequencyMinutes: syncFreq,
|
|
DeviceMetadata: []byte("{}"),
|
|
})
|
|
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to create device"})
|
|
}
|
|
|
|
delete(pendingRegistrations, req.RegistrationID)
|
|
|
|
syncEndpoints := map[string]string{}
|
|
switch registration.DeviceType {
|
|
case "koreader":
|
|
syncEndpoints["progress"] = fmt.Sprintf("%s/api/sync/koreader/progress", h.cfg.BaseURL)
|
|
syncEndpoints["metadata"] = fmt.Sprintf("%s/api/sync/koreader/metadata", h.cfg.BaseURL)
|
|
syncEndpoints["bookmarks"] = fmt.Sprintf("%s/api/sync/koreader/bookmarks", h.cfg.BaseURL)
|
|
case "kobo":
|
|
syncEndpoints["markup"] = fmt.Sprintf("%s/api/sync/kobo/markup", h.cfg.BaseURL)
|
|
syncEndpoints["library"] = fmt.Sprintf("%s/api/sync/kobo/library", h.cfg.BaseURL)
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, DeviceAuthStatusResponse{
|
|
Status: "approved",
|
|
AuthToken: authToken,
|
|
DeviceID: device.ID.Bytes,
|
|
SyncEndpoints: syncEndpoints,
|
|
})
|
|
}
|
|
|
|
func (h *DeviceHandler) ListDevices(c echo.Context) error {
|
|
userID := c.Get("user_id").(string)
|
|
userUUID, err := uuid.Parse(userID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user ID"})
|
|
}
|
|
|
|
pgUserID := pgtype.UUID{Bytes: [16]byte(userUUID), Valid: true}
|
|
|
|
devices, err := h.db.ListDevicesByUser(c.Request().Context(), pgUserID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to list devices"})
|
|
}
|
|
|
|
deviceList := make([]DeviceInfo, 0, len(devices))
|
|
for _, device := range devices {
|
|
syncEnabled := device.SyncEnabled.Bool && device.SyncEnabled.Valid
|
|
autoSync := device.AutoSync.Bool && device.AutoSync.Valid
|
|
syncFreq := int32(0)
|
|
if device.SyncFrequencyMinutes.Valid {
|
|
syncFreq = device.SyncFrequencyMinutes.Int32
|
|
}
|
|
|
|
deviceList = append(deviceList, DeviceInfo{
|
|
ID: device.ID.Bytes,
|
|
DeviceName: device.DeviceName,
|
|
DeviceType: device.DeviceType,
|
|
LastSync: (*time.Time)(&device.LastSync.Time),
|
|
LastSeen: (*time.Time)(&device.LastSeen.Time),
|
|
SyncEnabled: syncEnabled,
|
|
AutoSync: autoSync,
|
|
SyncFrequency: syncFreq,
|
|
CreatedAt: device.CreatedAt.Time,
|
|
DeviceMetadata: device.DeviceMetadata,
|
|
})
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, DeviceListResponse{
|
|
Devices: deviceList,
|
|
Total: len(deviceList),
|
|
})
|
|
}
|
|
|
|
func (h *DeviceHandler) GetDevice(c echo.Context) error {
|
|
userID := c.Get("user_id")
|
|
if userID == nil {
|
|
return c.JSON(http.StatusUnauthorized, map[string]string{"error": "unauthorized"})
|
|
}
|
|
|
|
userUUID, err := uuid.Parse(userID.(string))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user ID"})
|
|
}
|
|
|
|
deviceID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device ID"})
|
|
}
|
|
|
|
pgDeviceID := pgtype.UUID{Bytes: [16]byte(deviceID), Valid: true}
|
|
|
|
device, err := h.db.GetDevice(c.Request().Context(), pgDeviceID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusNotFound, map[string]string{"error": "device not found"})
|
|
}
|
|
|
|
if device.UserID.Bytes != userUUID {
|
|
return c.JSON(http.StatusForbidden, map[string]string{"error": "access denied"})
|
|
}
|
|
|
|
syncEnabled := device.SyncEnabled.Bool && device.SyncEnabled.Valid
|
|
autoSync := device.AutoSync.Bool && device.AutoSync.Valid
|
|
syncFreq := int32(0)
|
|
if device.SyncFrequencyMinutes.Valid {
|
|
syncFreq = device.SyncFrequencyMinutes.Int32
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, DeviceInfo{
|
|
ID: device.ID.Bytes,
|
|
DeviceName: device.DeviceName,
|
|
DeviceType: device.DeviceType,
|
|
LastSync: (*time.Time)(&device.LastSync.Time),
|
|
LastSeen: (*time.Time)(&device.LastSeen.Time),
|
|
SyncEnabled: syncEnabled,
|
|
AutoSync: autoSync,
|
|
SyncFrequency: syncFreq,
|
|
CreatedAt: device.CreatedAt.Time,
|
|
DeviceMetadata: device.DeviceMetadata,
|
|
})
|
|
}
|
|
|
|
func (h *DeviceHandler) UpdateDevice(c echo.Context) error {
|
|
userID := c.Get("user_id").(string)
|
|
userUUID, err := uuid.Parse(userID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user ID"})
|
|
}
|
|
|
|
deviceID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device ID"})
|
|
}
|
|
|
|
pgDeviceID := pgtype.UUID{Bytes: [16]byte(deviceID), Valid: true}
|
|
|
|
device, err := h.db.GetDevice(c.Request().Context(), pgDeviceID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusNotFound, map[string]string{"error": "device not found"})
|
|
}
|
|
|
|
if device.UserID.Bytes != userUUID {
|
|
return c.JSON(http.StatusForbidden, map[string]string{"error": "access denied"})
|
|
}
|
|
|
|
req := DeviceUpdateRequest{}
|
|
if err := c.Bind(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid request format"})
|
|
}
|
|
|
|
if err := c.Validate(&req); err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": err.Error()})
|
|
}
|
|
|
|
updateParams := database.UpdateDeviceParams{
|
|
ID: pgDeviceID,
|
|
}
|
|
|
|
if req.DeviceName != "" {
|
|
updateParams.DeviceName = req.DeviceName
|
|
} else {
|
|
updateParams.DeviceName = device.DeviceName
|
|
}
|
|
|
|
if req.SyncEnabled != nil {
|
|
updateParams.SyncEnabled = pgtype.Bool{Bool: *req.SyncEnabled, Valid: true}
|
|
} else {
|
|
updateParams.SyncEnabled = device.SyncEnabled
|
|
}
|
|
|
|
if req.AutoSync != nil {
|
|
updateParams.AutoSync = pgtype.Bool{Bool: *req.AutoSync, Valid: true}
|
|
} else {
|
|
updateParams.AutoSync = device.AutoSync
|
|
}
|
|
|
|
if req.SyncFrequencyMinutes != nil {
|
|
updateParams.SyncFrequencyMinutes = pgtype.Int4{Int32: *req.SyncFrequencyMinutes, Valid: true}
|
|
} else {
|
|
updateParams.SyncFrequencyMinutes = device.SyncFrequencyMinutes
|
|
}
|
|
|
|
if req.DeviceMetadata != nil {
|
|
updateParams.DeviceMetadata = req.DeviceMetadata
|
|
} else {
|
|
updateParams.DeviceMetadata = device.DeviceMetadata
|
|
}
|
|
|
|
updatedDevice, err := h.db.UpdateDevice(c.Request().Context(), updateParams)
|
|
if err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to update device"})
|
|
}
|
|
|
|
syncEnabled := updatedDevice.SyncEnabled.Bool && updatedDevice.SyncEnabled.Valid
|
|
autoSync := updatedDevice.AutoSync.Bool && updatedDevice.AutoSync.Valid
|
|
syncFreq := int32(0)
|
|
if updatedDevice.SyncFrequencyMinutes.Valid {
|
|
syncFreq = updatedDevice.SyncFrequencyMinutes.Int32
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, map[string]interface{}{
|
|
"device_updated": true,
|
|
"device": DeviceInfo{
|
|
ID: updatedDevice.ID.Bytes,
|
|
DeviceName: updatedDevice.DeviceName,
|
|
DeviceType: updatedDevice.DeviceType,
|
|
LastSync: (*time.Time)(&updatedDevice.LastSync.Time),
|
|
LastSeen: (*time.Time)(&updatedDevice.LastSeen.Time),
|
|
SyncEnabled: syncEnabled,
|
|
AutoSync: autoSync,
|
|
SyncFrequency: syncFreq,
|
|
CreatedAt: updatedDevice.CreatedAt.Time,
|
|
DeviceMetadata: updatedDevice.DeviceMetadata,
|
|
},
|
|
})
|
|
}
|
|
|
|
func (h *DeviceHandler) DeleteDevice(c echo.Context) error {
|
|
userID := c.Get("user_id").(string)
|
|
userUUID, err := uuid.Parse(userID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user ID"})
|
|
}
|
|
|
|
deviceID, err := uuid.Parse(c.Param("id"))
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid device ID"})
|
|
}
|
|
|
|
pgDeviceID := pgtype.UUID{Bytes: [16]byte(deviceID), Valid: true}
|
|
|
|
device, err := h.db.GetDevice(c.Request().Context(), pgDeviceID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusNotFound, map[string]string{"error": "device not found"})
|
|
}
|
|
|
|
if device.UserID.Bytes != userUUID {
|
|
return c.JSON(http.StatusForbidden, map[string]string{"error": "access denied"})
|
|
}
|
|
|
|
if err := h.db.DeleteDevice(c.Request().Context(), pgDeviceID); err != nil {
|
|
return c.JSON(http.StatusInternalServerError, map[string]string{"error": "failed to delete device"})
|
|
}
|
|
|
|
return c.NoContent(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *DeviceHandler) ApproveDevice(c echo.Context) error {
|
|
userID := c.Get("user_id").(string)
|
|
userUUID, err := uuid.Parse(userID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user ID"})
|
|
}
|
|
|
|
registrationID := c.Param("registration_id")
|
|
|
|
registration, exists := pendingRegistrations[registrationID]
|
|
if !exists {
|
|
return c.JSON(http.StatusNotFound, map[string]string{"error": "registration not found"})
|
|
}
|
|
|
|
if time.Now().After(registration.ExpiresAt) {
|
|
delete(pendingRegistrations, registrationID)
|
|
return c.JSON(http.StatusGone, map[string]string{"error": "registration expired"})
|
|
}
|
|
|
|
registration.UserID = userUUID
|
|
|
|
return c.JSON(http.StatusOK, map[string]interface{}{
|
|
"message": "device approved successfully",
|
|
"device_name": registration.DeviceName,
|
|
"device_type": registration.DeviceType,
|
|
"registration_id": registrationID,
|
|
})
|
|
}
|
|
|
|
func (h *DeviceHandler) RejectDevice(c echo.Context) error {
|
|
registrationID := c.Param("registration_id")
|
|
|
|
_, exists := pendingRegistrations[registrationID]
|
|
if !exists {
|
|
return c.JSON(http.StatusNotFound, map[string]string{"error": "registration not found"})
|
|
}
|
|
|
|
delete(pendingRegistrations, registrationID)
|
|
|
|
return c.JSON(http.StatusOK, map[string]string{
|
|
"message": "device registration rejected",
|
|
})
|
|
}
|
|
|
|
func (h *DeviceHandler) GetPendingRegistrationsData(c echo.Context) ([]map[string]interface{}, error) {
|
|
userID := c.Get("user_id").(string)
|
|
userUUID, err := uuid.Parse(userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
registrations := []map[string]interface{}{}
|
|
for _, reg := range pendingRegistrations {
|
|
if reg.UserID == userUUID || reg.UserID == (uuid.UUID{}) {
|
|
registrations = append(registrations, map[string]interface{}{
|
|
"registration_id": reg.RegistrationID,
|
|
"device_name": reg.DeviceName,
|
|
"device_type": reg.DeviceType,
|
|
"device_identifier": reg.DeviceIdentifier,
|
|
"expires_at": reg.ExpiresAt,
|
|
"created_at": reg.CreatedAt,
|
|
"is_approved": reg.UserID != (uuid.UUID{}),
|
|
})
|
|
}
|
|
}
|
|
|
|
return registrations, nil
|
|
}
|
|
|
|
func (h *DeviceHandler) ListPendingRegistrations(c echo.Context) error {
|
|
userID := c.Get("user_id").(string)
|
|
userUUID, err := uuid.Parse(userID)
|
|
if err != nil {
|
|
return c.JSON(http.StatusBadRequest, map[string]string{"error": "invalid user ID"})
|
|
}
|
|
|
|
registrations := []map[string]interface{}{}
|
|
for _, reg := range pendingRegistrations {
|
|
if reg.UserID == userUUID || reg.UserID == (uuid.UUID{}) {
|
|
registrations = append(registrations, map[string]interface{}{
|
|
"registration_id": reg.RegistrationID,
|
|
"device_name": reg.DeviceName,
|
|
"device_type": reg.DeviceType,
|
|
"device_identifier": reg.DeviceIdentifier,
|
|
"expires_at": reg.ExpiresAt,
|
|
"created_at": reg.CreatedAt,
|
|
"is_approved": reg.UserID != (uuid.UUID{}),
|
|
})
|
|
}
|
|
}
|
|
|
|
return c.JSON(http.StatusOK, map[string]interface{}{
|
|
"registrations": registrations,
|
|
"total": len(registrations),
|
|
})
|
|
}
|
|
|
|
func generateDeviceToken() (string, error) {
|
|
bytes := make([]byte, 32)
|
|
if _, err := rand.Read(bytes); err != nil {
|
|
return "", err
|
|
}
|
|
|
|
hashedToken, err := bcrypt.GenerateFromPassword(bytes, bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
token := base64.StdEncoding.EncodeToString(hashedToken)
|
|
|
|
return fmt.Sprintf("dev_%s", token), nil
|
|
}
|
|
|
|
func ValidateDeviceToken(hashedToken string, plainToken string) bool {
|
|
err := bcrypt.CompareHashAndPassword([]byte(hashedToken), []byte(plainToken))
|
|
return err == nil
|
|
}
|