(ns metabase.login-history.models.login-history (:require [java-time.api :as t] [metabase.request.core :as request] [metabase.util.date-2 :as u.date] [metabase.util.i18n :as i18n :refer [tru]] [metabase.util.malli :as mu] [metabase.util.malli.schema :as ms] [methodical.core :as methodical] [toucan2.core :as t2] [toucan2.realize :as t2.realize])) | |
(set! *warn-on-reflection* true) | |
(defn- timezone-display-name [^java.time.ZoneId zone-id]
(when zone-id
(.getDisplayName zone-id
java.time.format.TextStyle/SHORT_STANDALONE
(i18n/user-locale)))) | |
Return human-friendly versions of the info in one or more LoginHistory instances. Powers the login history API endpoint and login on new device email. This can potentially take a few seconds to complete, if the request to geocode the API request hangs for one reason or another -- keep that in mind when using this. | (defn human-friendly-infos
[history-items]
(let [ip-addresses (map :ip_address history-items)
ip->info (request/geocode-ip-addresses ip-addresses)]
(for [history-item history-items
:let [{location-description :description, timezone :timezone} (get ip->info (:ip_address history-item))]]
(-> history-item
(assoc :location location-description
:timezone (timezone-display-name timezone))
(update :timestamp (fn [timestamp]
(if (and timestamp timezone)
(t/zoned-date-time (u.date/with-time-zone-same-instant timestamp timezone) timezone)
timestamp)))
(update :device_description request/describe-user-agent))))) |
(methodical/defmethod t2/table-name :model/LoginHistory [_model] :login_history) | |
(doto :model/LoginHistory (derive :metabase/model)) | |
Record a login event in the LoginHistory table. | (mu/defn record-login-history!
[session-id :- uuid?
user-id :- ms/PositiveInt
device-info :- request/DeviceInfo]
(let [login-history (merge {:user_id user-id
:session_id (str session-id)}
(dissoc device-info :embedded))]
(t2/insert! :model/LoginHistory login-history)
login-history)) |
(t2/define-after-select :model/LoginHistory
[{session-id :session_id, :as login-history}]
;; session ID is sensitive, so it's better if we don't even return it. Replace it with a more generic `active` key.
(cond-> (t2.realize/realize login-history)
(contains? login-history :session_id) (assoc :active (boolean session-id))
true (dissoc :session_id))) | |
Return true if this is the first login ever for the given user-id. | (defn first-login-ever?
[{user-id :user_id}]
(some-> (t2/select [:model/LoginHistory :id] :user_id user-id {:limit 2})
count
(= 1))) |
Return true if this is the first login for the given user-id on the device | (defn first-login-on-this-device?
[{user-id :user_id, device-id :device_id}]
(some-> (t2/select [:model/LoginHistory :id] :user_id user-id, :device_id device-id, {:limit 2})
count
(= 1))) |
(t2/define-before-update :model/LoginHistory [_login-history] (throw (RuntimeException. (tru "You can''t update a LoginHistory after it has been created.")))) | |