113 lines
3.0 KiB
Go
113 lines
3.0 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type authenticatedDevice struct {
|
|
DeviceID string
|
|
UserID string
|
|
VaultID string
|
|
}
|
|
|
|
func (s *Server) requireAuth(w http.ResponseWriter, r *http.Request) (deviceID, userID string, ok bool) {
|
|
device, ok := s.authenticateDevice(w, r)
|
|
if !ok {
|
|
return "", "", false
|
|
}
|
|
return device.DeviceID, device.UserID, true
|
|
}
|
|
|
|
func (s *Server) requireSyncScope(w http.ResponseWriter, r *http.Request) (authenticatedDevice, bool) {
|
|
device, ok := s.authenticateDevice(w, r)
|
|
if !ok {
|
|
return authenticatedDevice{}, false
|
|
}
|
|
if device.UserID == "" {
|
|
jsonErr(w, http.StatusForbidden, "device is not associated with a user")
|
|
return authenticatedDevice{}, false
|
|
}
|
|
device.VaultID = effectiveVaultScope(device.UserID, device.VaultID)
|
|
if device.VaultID == "" {
|
|
jsonErr(w, http.StatusForbidden, "device is not associated with a vault")
|
|
return authenticatedDevice{}, false
|
|
}
|
|
return device, true
|
|
}
|
|
|
|
func (s *Server) authenticateDevice(w http.ResponseWriter, r *http.Request) (authenticatedDevice, bool) {
|
|
key := r.Header.Get("Authorization")
|
|
key = strings.TrimPrefix(key, "Bearer ")
|
|
if key == "" {
|
|
key = r.URL.Query().Get("api_key")
|
|
}
|
|
if key == "" {
|
|
jsonErr(w, 401, "API key required")
|
|
return authenticatedDevice{}, false
|
|
}
|
|
|
|
var device authenticatedDevice
|
|
var userID, vaultID, revokedAt sql.NullString
|
|
err := s.db.QueryRow(`SELECT id, user_id, vault_id, revoked_at
|
|
FROM server_devices WHERE token_hash=?`, sha256Hex(key)).
|
|
Scan(&device.DeviceID, &userID, &vaultID, &revokedAt)
|
|
if err != nil {
|
|
err = s.db.QueryRow(`SELECT id, user_id, vault_id, revoked_at
|
|
FROM server_devices WHERE api_key=?`, key).
|
|
Scan(&device.DeviceID, &userID, &vaultID, &revokedAt)
|
|
}
|
|
if err != nil {
|
|
jsonErr(w, 401, "invalid API key")
|
|
return authenticatedDevice{}, false
|
|
}
|
|
if revokedAt.Valid && revokedAt.String != "" {
|
|
jsonErr(w, 401, "device revoked")
|
|
return authenticatedDevice{}, false
|
|
}
|
|
if userID.Valid {
|
|
device.UserID = userID.String
|
|
}
|
|
if vaultID.Valid {
|
|
device.VaultID = vaultID.String
|
|
}
|
|
if device.UserID != "" {
|
|
var blocked int
|
|
s.db.QueryRow("SELECT blocked FROM server_users WHERE id=?", device.UserID).Scan(&blocked)
|
|
if blocked != 0 {
|
|
jsonErr(w, 403, "user blocked")
|
|
return authenticatedDevice{}, false
|
|
}
|
|
}
|
|
s.db.Exec("UPDATE server_devices SET last_seen=? WHERE id=?", time.Now().UTC().Format(time.RFC3339), device.DeviceID)
|
|
return device, true
|
|
}
|
|
|
|
func (s *Server) requireAdmin(w http.ResponseWriter, r *http.Request) bool {
|
|
cookie, err := r.Cookie("session")
|
|
if err != nil || !s.tokens.Check(cookie.Value) {
|
|
http.Redirect(w, r, "/admin/login", http.StatusFound)
|
|
return false
|
|
}
|
|
return true
|
|
}
|
|
|
|
type PasswordError string
|
|
|
|
const (
|
|
ErrPasswordTooShort PasswordError = "PASSWORD_TOO_SHORT"
|
|
ErrPasswordTooLong PasswordError = "PASSWORD_TOO_LONG"
|
|
)
|
|
|
|
func validatePassword(password string) string {
|
|
if len(password) < 8 {
|
|
return string(ErrPasswordTooShort)
|
|
}
|
|
if len(password) > 256 {
|
|
return string(ErrPasswordTooLong)
|
|
}
|
|
return ""
|
|
}
|