Skip to main content

nordnet_api/resources/
login.rs

1//! Resource methods for the `login` API group.
2//!
3//! # Operations
4//!
5//! | Method | Op | Path |
6//! |--------|------------------|--------------------|
7//! | POST   | `start_login`    | `/login/start`     |
8//! | POST   | `verify_login`   | `/login/verify`    |
9//! | PUT    | `refresh_session`| `/login`           |
10//! | DELETE | `logout`         | `/login`           |
11//!
12//!
13//! ## Body-less PUT
14//!
15//! `refresh_session` is documented with no request body. We use the
16//! foundation [`Client::put_empty`] helper, which omits the
17//! `Content-Type` header and sends a zero-length payload — the shape
18//! Nordnet's `PUT /login` expects.
19
20use crate::client::Client;
21use crate::error::Error;
22use nordnet_model::auth::{ApiKeyStartLoginRequest, ApiKeyVerifyLoginRequest, ChallengeResponse};
23use nordnet_model::models::login::{ApiKeyLoginResponse, LoggedInStatus};
24
25impl Client {
26    /// `POST /login/start` — Start the authentication challenge.
27    ///
28    /// Returns a [`ChallengeResponse`] whose `challenge` field must be
29    /// signed with the caller's RSA private key (see
30    /// [`nordnet_model::auth::sign_challenge`]) before being passed to
31    /// [`Client::verify_login`]. The challenge is valid for 30 seconds.
32    ///
33    /// # Errors
34    ///
35    /// Returns [`Error::BadRequest`] (400), [`Error::TooManyRequests`]
36    /// (429), or [`Error::ServiceUnavailable`] (503) per the docs.
37    #[doc(alias = "POST /login/start")]
38    pub async fn start_login(
39        &self,
40        request: &ApiKeyStartLoginRequest,
41    ) -> Result<ChallengeResponse, Error> {
42        self.post("/login/start", request).await
43    }
44
45    /// `POST /login/verify` — Complete the login flow with a signed
46    /// challenge.
47    ///
48    /// On success the returned [`ApiKeyLoginResponse`] carries the
49    /// `session_key`. Convert it to a [`nordnet_model::auth::Session`] via
50    /// [`ApiKeyLoginResponse::to_session`] and attach it with
51    /// [`Client::with_session`] for subsequent authenticated calls.
52    ///
53    /// # Errors
54    ///
55    /// Returns [`Error::BadRequest`] (400), [`Error::Unauthorized`] (401),
56    /// [`Error::TooManyRequests`] (429), or [`Error::ServiceUnavailable`]
57    /// (503) per the docs.
58    #[doc(alias = "POST /login/verify")]
59    pub async fn verify_login(
60        &self,
61        request: &ApiKeyVerifyLoginRequest,
62    ) -> Result<ApiKeyLoginResponse, Error> {
63        self.post("/login/verify", request).await
64    }
65
66    /// `PUT /login` — Touch the session to keep it alive.
67    ///
68    /// Any other authenticated call also touches the session, so this is
69    /// only needed when the application is otherwise idle for the full
70    /// session timeout interval.
71    ///
72    /// # Errors
73    ///
74    /// Returns [`Error::BadRequest`] (400), [`Error::Unauthorized`] (401),
75    /// [`Error::TooManyRequests`] (429), or [`Error::ServiceUnavailable`]
76    /// (503) per the docs.
77    #[doc(alias = "PUT /login")]
78    pub async fn refresh_session(&self) -> Result<LoggedInStatus, Error> {
79        self.put_empty("/login").await
80    }
81
82    /// `DELETE /login` — Invalidate the current session.
83    ///
84    /// # Errors
85    ///
86    /// Returns [`Error::BadRequest`] (400), [`Error::Unauthorized`] (401),
87    /// [`Error::TooManyRequests`] (429), or [`Error::ServiceUnavailable`]
88    /// (503) per the docs.
89    #[doc(alias = "DELETE /login")]
90    pub async fn logout(&self) -> Result<LoggedInStatus, Error> {
91        self.delete("/login").await
92    }
93}