Module: Leash::Auth

Defined in:
lib/leash/auth.rb

Overview

Framework-agnostic server auth helper.

Works with any request object that exposes either:

- request.cookies (Hash) — Rack / Rails / Sinatra
- request.env['HTTP_COOKIE'] or request.get_header('HTTP_COOKIE') — raw Rack env

Does NOT require rails, sinatra, or rack.

Constant Summary collapse

"leash-auth"

Class Method Summary collapse

Class Method Details

.authenticated?(request) ⇒ Boolean

Check whether the request carries a valid leash-auth cookie.

Parameters:

  • request (#cookies, #env, #get_header)

Returns:

  • (Boolean)


67
68
69
70
71
72
# File 'lib/leash/auth.rb', line 67

def authenticated?(request)
  get_user(request)
  true
rescue AuthError
  false
end

.build_user(payload) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Raises:



128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/leash/auth.rb', line 128

def build_user(payload)
  id = payload["id"] || payload["sub"]
  email = payload["email"]
  raise AuthError, "Token payload missing required fields (id/sub, email)" unless id && email

  User.new(
    id: id,
    email: email,
    name: payload["name"],
    picture: payload["picture"]
  )
end

.decode_token(token) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/leash/auth.rb', line 113

def decode_token(token)
  secret = ENV["LEASH_JWT_SECRET"]
  if secret && !secret.empty?
    decoded = JWT.decode(token, secret, true, algorithms: ["HS256"])
  else
    decoded = JWT.decode(token, nil, false)
  end
  decoded.first
rescue JWT::ExpiredSignature
  raise AuthError, "Token has expired"
rescue JWT::DecodeError => e
  raise AuthError, "Invalid token: #{e.message}"
end

.extract_token(request) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'lib/leash/auth.rb', line 75

def extract_token(request)
  # Strategy 1: request.cookies hash (Rack / Rails / Sinatra)
  if request.respond_to?(:cookies)
    cookies = request.cookies
    if cookies.is_a?(Hash)
      value = cookies[COOKIE_NAME] || cookies[COOKIE_NAME.to_sym]
      return value if value
    end
  end

  # Strategy 2: raw Cookie header from env or get_header
  raw = nil
  if request.respond_to?(:env) && request.env.is_a?(Hash)
    raw = request.env["HTTP_COOKIE"]
  end
  if raw.nil? && request.respond_to?(:get_header)
    begin
      raw = request.get_header("HTTP_COOKIE")
    rescue StandardError
      nil
    end
  end

  parse_cookie_header(raw) if raw
end

.get_user(request) ⇒ Leash::User

Read the leash-auth JWT from the request, decode it, and return a User.

Parameters:

  • request (#cookies, #env, #get_header)

    any Rack-like request object

Returns:

Raises:

  • (Leash::AuthError)

    when the cookie is missing or the token is invalid/expired



55
56
57
58
59
60
61
# File 'lib/leash/auth.rb', line 55

def get_user(request)
  token = extract_token(request)
  raise AuthError, "Missing leash-auth cookie" if token.nil? || token.empty?

  payload = decode_token(token)
  build_user(payload)
end

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



102
103
104
105
106
107
108
109
110
# File 'lib/leash/auth.rb', line 102

def parse_cookie_header(header)
  return nil if header.nil?

  header.split(";").each do |pair|
    key, value = pair.strip.split("=", 2)
    return value if key == COOKIE_NAME
  end
  nil
end