Add automatic refresh of authentication tokens

This commit is contained in:
Mikko Ahlroth 2023-09-23 10:21:02 +03:00
parent bea99ea701
commit b08bd39fef
5 changed files with 74 additions and 5 deletions

View file

@ -17,6 +17,10 @@ export function to_iso8601(date) {
return date.toISOString(); return date.toISOString();
} }
export function to_unix(date) {
return date.getTime();
}
export function unix_now() { export function unix_now() {
return Date.now(); return Date.now();
} }

View file

@ -12,6 +12,7 @@ pub type Config {
debug: Bool, debug: Bool,
api_timeout: Int, api_timeout: Int,
api_auth_url: Uri, api_auth_url: Uri,
api_auth_token_refresh_diff: Int,
api_installations_url: Uri, api_installations_url: Uri,
api_device_url: Uri, api_device_url: Uri,
api_device_status_url: Uri, api_device_status_url: Uri,
@ -38,6 +39,10 @@ pub fn load_config() -> Config {
config_helpers.api_auth_url, config_helpers.api_auth_url,
"https://thermia-auth-api.azurewebsites.net/api/v1/Jwt/login", "https://thermia-auth-api.azurewebsites.net/api/v1/Jwt/login",
), ),
api_auth_token_refresh_diff: get_env(
config_helpers.api_auth_token_refresh_diff,
300_000,
),
api_installations_url: get_uri( api_installations_url: get_uri(
config_helpers.api_installations_url, config_helpers.api_installations_url,
"https://online-genesis-serviceapi.azurewebsites.net/api/v1/installationsInfo", "https://online-genesis-serviceapi.azurewebsites.net/api/v1/installationsInfo",

View file

@ -16,6 +16,10 @@ pub fn api_auth_url() {
config_url("api_auth_url") config_url("api_auth_url")
} }
pub fn api_auth_token_refresh_diff() {
config("api_auth_token_refresh_diff", dynamic.int)
}
pub fn api_installations_url() { pub fn api_installations_url() {
config_url("api_installations_url") config_url("api_installations_url")
} }

View file

@ -13,6 +13,9 @@ pub fn to_iso8601(d: Date) -> String
@external(javascript, "../../date_ffi.mjs", "from_unix") @external(javascript, "../../date_ffi.mjs", "from_unix")
pub fn from_unix(a: Int) -> Result(Date, Nil) pub fn from_unix(a: Int) -> Result(Date, Nil)
@external(javascript, "../../date_ffi.mjs", "to_unix")
pub fn to_unix(a: Date) -> Int
@external(javascript, "../../date_ffi.mjs", "now") @external(javascript, "../../date_ffi.mjs", "now")
pub fn now() -> Date pub fn now() -> Date

View file

@ -1,11 +1,15 @@
import gleam/option.{None, Option, Some} import gleam/option.{None, Option, Some}
import gleam/order
import gleam/string import gleam/string
import gleam/io
import gleam/javascript/promise import gleam/javascript/promise
import lustre import lustre
import lustre/element/html import lustre/element/html
import lustre/effect.{Effect} import lustre/effect.{Effect}
import lustre/element import lustre/element
import plinth/javascript/storage.{Storage} import plinth/javascript/storage.{Storage}
import geo_t/helpers/timers
import geo_t/helpers/date
import geo_t/config.{Config} import geo_t/config.{Config}
import geo_t/web/login_view import geo_t/web/login_view
import geo_t/web/installations_view import geo_t/web/installations_view
@ -15,6 +19,8 @@ import geo_t/pump_api/api.{ApiError}
import geo_t/pump_api/auth/api as auth_api import geo_t/pump_api/auth/api as auth_api
import geo_t/web/auth.{AuthInfo, AuthInfoStorage} import geo_t/web/auth.{AuthInfo, AuthInfoStorage}
const auth_update_check_interval = 10_000
pub fn main() { pub fn main() {
let app = lustre.application(init, update, view) let app = lustre.application(init, update, view)
let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil) let assert Ok(_) = lustre.start(app, "[data-lustre-app]", Nil)
@ -41,6 +47,8 @@ pub type Msg {
LoginResult(Result(User, ApiError)) LoginResult(Result(User, ApiError))
InstallationsView(installations_view.Msg) InstallationsView(installations_view.Msg)
PumpView(pump_view.Msg) PumpView(pump_view.Msg)
AuthRefreshCheck
RefreshLogin
} }
fn init(_) { fn init(_) {
@ -52,6 +60,9 @@ fn init(_) {
Error(_) -> None Error(_) -> None
} }
let auth_refresh_effect =
effect.from(fn(dispatch) { auth_refresh_check_timer(dispatch) })
let #(inst_model, inst_effect) = case auth_info { let #(inst_model, inst_effect) = case auth_info {
Some(info) -> Some(info) ->
update_installations( update_installations(
@ -73,13 +84,15 @@ fn init(_) {
logging_in: False, logging_in: False,
logged_in: option.is_some(auth_info), logged_in: option.is_some(auth_info),
), ),
inst_effect, effect.batch([auth_refresh_effect, inst_effect]),
) )
} }
fn update(model: Model, msg: Msg) { fn update(model: Model, msg: Msg) {
case msg { case msg {
LoginView(login_view.AttemptLogin) -> { AuthRefreshCheck -> #(model, auth_refresh_check(model))
RefreshLogin | LoginView(login_view.AttemptLogin) -> {
#( #(
Model( Model(
..model, ..model,
@ -164,11 +177,11 @@ fn view(model: Model) {
html.div( html.div(
[], [],
[ [
case model.logged_in { case model.auth {
False -> None ->
login_view.view(model.login) login_view.view(model.login)
|> element.map(LoginView) |> element.map(LoginView)
True -> { Some(_) -> {
case model.pump { case model.pump {
None -> None ->
installations_view.view(model.installations) installations_view.view(model.installations)
@ -183,6 +196,46 @@ fn view(model: Model) {
) )
} }
fn auth_refresh_check_timer(dispatch) {
timers.set_timeout(
fn() { dispatch(AuthRefreshCheck) },
auth_update_check_interval,
)
Nil
}
fn auth_refresh_check(model: Model) {
use dispatch <- effect.from()
auth_refresh_check_timer(dispatch)
case model.auth {
Some(auth) -> {
let now = date.now()
let assert Ok(last_valid_time) =
date.from_unix(
date.to_unix(auth.user.tokens.access_token_expiry) - model.config.api_auth_token_refresh_diff,
)
case date.compare(now, last_valid_time) {
order.Gt -> {
io.println("[AuthCheck] Auth expiring soon, refreshing...")
dispatch(RefreshLogin)
Nil
}
order.Lt | order.Eq -> {
io.println("[AuthCheck] Auth not expiring yet, no need to refresh.")
}
}
Nil
}
None -> Nil
}
}
fn login(model: Model) -> Effect(Msg) { fn login(model: Model) -> Effect(Msg) {
use dispatch <- effect.from() use dispatch <- effect.from()