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}