Compare commits
3 Commits
56e23f3da0
...
fdee7c16ca
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fdee7c16ca | ||
|
|
4d08994e79 | ||
|
|
bbdfe75866 |
@@ -401,7 +401,7 @@ or:
|
||||
|
||||
**Response on success:** Full user object (same shape as `GET /v3/crud/user/{id}`).
|
||||
|
||||
**Errors:** `400` missing credentials, `403` wrong password or account disabled, `404` user not found.
|
||||
**Errors:** `400` missing credentials, `403` wrong password / account disabled / account not yet enabled / account expired, `404` user not found.
|
||||
|
||||
> **Auth key flow:** Auth keys are one-time-use — the key is cleared from the DB immediately on successful authentication. Request a new one via `GET /v3/action/user/{id}/new_auth_key`.
|
||||
|
||||
@@ -421,7 +421,7 @@ Check a user's current password without changing it.
|
||||
```
|
||||
or use `"username"` instead of `"user_id"` to look up by username within the account.
|
||||
|
||||
**Response:** `data: true` on match. `403` on mismatch, `404` if user not found.
|
||||
**Response:** `data: true` on match. `400` if the user has no password set, `403` on mismatch, `404` if user not found.
|
||||
|
||||
---
|
||||
|
||||
@@ -474,10 +474,19 @@ Generate a new auth key and email a one-time login link to the user's email addr
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|---|---|---|---|
|
||||
| `root_url` | `string` | `null` | Base URL the login link is built from. |
|
||||
| `root_url` | `string` | *(required)* | Base URL the login link is built from. Must be provided — if omitted the link in the email will be malformed (`None?...`). |
|
||||
| `key_param_name` | `string` | `auth_key` | Query param name used for the auth key in the generated link. |
|
||||
|
||||
**Response:** `data: true` on success (email sent). `500` if delivery failed (check account email config and that the user account is enabled with `allow_auth_key = true`).
|
||||
> [!IMPORTANT]
|
||||
> `root_url` is **required in practice**. The FastAPI query param accepts `null` but the email builder does not guard against it — omitting it produces a broken link in the email.
|
||||
|
||||
**Magic link URL format (default `key_param_name`):**
|
||||
```
|
||||
{root_url}?user_id={user_id_random}&auth_key={auth_key}&valid_email=True
|
||||
```
|
||||
The frontend at `root_url` should read these query params and call `POST /v3/action/user/authenticate` with `{ "user_id": "...", "auth_key": "..." }`. Note that `valid_email=True` is **always** injected — authenticating via a magic link automatically marks the user's email as verified.
|
||||
|
||||
**Response:** `data: true` on success (email sent). `404` if user not found. `500` if delivery failed — common causes: account email not configured, user `enable = false`, or `allow_auth_key = false`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -328,117 +328,76 @@ export async function delete_ae_obj_id__user({
|
||||
}
|
||||
|
||||
/*
|
||||
* *** LEGACY AUTHENTICATION HEADER LOGIC ***
|
||||
* *** V3 AUTHENTICATION FUNCTIONS ***
|
||||
*
|
||||
* The functions in this section interact with legacy Aether API authentication endpoints
|
||||
* (e.g., /user/authenticate, /user/lookup_email).
|
||||
* All functions below use the V3 action endpoints:
|
||||
* POST /v3/action/user/authenticate (was: GET /user/authenticate)
|
||||
* GET /v3/action/user/{id}/email_auth_key_url (was: GET /user/{id}/email_auth_key_url)
|
||||
* POST /v3/crud/user/search (was: GET /user/lookup_email)
|
||||
* POST /v3/action/user/{id}/change_password (was: PATCH /user/{id}/change_password)
|
||||
*
|
||||
* Unlike V3 endpoints which handle context automatically or via standard headers,
|
||||
* these legacy endpoints have specific requirements:
|
||||
*
|
||||
* 1. They often require the `x-account-id` header to be explicitly set to the target
|
||||
* account ID to find the user within that specific account context.
|
||||
* 2. The standard API wrapper logic might strip `x-account-id` if `x-no-account-id`
|
||||
* is present (Bootstrap Paradox logic). We must explicitly remove `x-no-account-id`
|
||||
* and set `x-account-id` to ensure the request is routed correctly.
|
||||
* 3. Some endpoints accept `account_id` as a query parameter, while others (like email sending)
|
||||
* may crash (500 Error) if unexpected parameters are passed.
|
||||
* Key differences from the legacy routes:
|
||||
* - Credentials are in the POST body, not query params (safer — not logged in URLs)
|
||||
* - x-account-id header still required to scope username/email lookups to the account
|
||||
* - x-no-account-id must still be removed when we have a real account context
|
||||
*/
|
||||
|
||||
// Updated 2025-04-04
|
||||
// This function handles username/password authentication.
|
||||
// It explicitly sets the x-account-id header to ensure the user is looked up in the correct account.
|
||||
// Updated 2026-04-25 — migrated from GET /user/authenticate to POST /v3/action/user/authenticate
|
||||
export async function auth_ae_obj__username_password({
|
||||
api_cfg,
|
||||
account_id,
|
||||
null_account_id = false,
|
||||
username,
|
||||
password,
|
||||
params = {},
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
account_id: string;
|
||||
null_account_id?: boolean;
|
||||
username: string;
|
||||
password: string;
|
||||
params?: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
if (log_lvl) {
|
||||
console.log(
|
||||
`*** auth_ae_obj__username_password() *** account_id=${account_id} username=${username} password=${password}`
|
||||
`*** auth_ae_obj__username_password() *** account_id=${account_id} username=${username}`
|
||||
);
|
||||
}
|
||||
|
||||
const endpoint = '/user/authenticate';
|
||||
|
||||
// Prepare API config with correct headers to override global guest settings
|
||||
// WHY: Must set x-account-id explicitly so the backend scopes the username lookup
|
||||
// to the correct account. Remove x-no-account-id which is only used during bootstrap.
|
||||
const use_api_cfg = { ...api_cfg, headers: { ...api_cfg.headers } };
|
||||
if (account_id) {
|
||||
use_api_cfg.headers['x-account-id'] = account_id;
|
||||
delete use_api_cfg.headers['x-no-account-id'];
|
||||
params['account_id'] = account_id;
|
||||
}
|
||||
|
||||
if (null_account_id) {
|
||||
params['null_account_id'] = true;
|
||||
}
|
||||
params['username'] = username; // Required
|
||||
params['password'] = password; // Required
|
||||
params['inc_jwt'] = true; // Request a JWT in the response
|
||||
if (log_lvl > 1) {
|
||||
console.log(`auth_ae_obj__username_password() - params:`, params);
|
||||
}
|
||||
use_api_cfg.headers['x-account-id'] = account_id;
|
||||
delete use_api_cfg.headers['x-no-account-id'];
|
||||
|
||||
ae_promises.auth__username_password = await api
|
||||
.get_object({
|
||||
.post_object({
|
||||
api_cfg: use_api_cfg,
|
||||
endpoint: endpoint,
|
||||
params: params,
|
||||
// data: {},
|
||||
log_lvl: log_lvl
|
||||
endpoint: '/v3/action/user/authenticate',
|
||||
data: { username, password },
|
||||
log_lvl
|
||||
})
|
||||
.then(async function (user_obj_get_result) {
|
||||
if (user_obj_get_result) {
|
||||
return user_obj_get_result;
|
||||
} else {
|
||||
console.log('No results returned.');
|
||||
return null;
|
||||
}
|
||||
.then(function (result: any) {
|
||||
return result ?? null;
|
||||
})
|
||||
.catch(function (error: any) {
|
||||
console.log('No results returned or failed.', error);
|
||||
console.log('auth_ae_obj__username_password failed:', error);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (log_lvl) {
|
||||
console.log(
|
||||
'ae_promises.auth__username_password:',
|
||||
ae_promises.auth__username_password
|
||||
);
|
||||
}
|
||||
return ae_promises.auth__username_password;
|
||||
}
|
||||
|
||||
// Updated 2025-04-04
|
||||
// This function handles authentication using a User ID and a one-time auth key.
|
||||
// Updated 2026-04-25 — migrated from GET /user/authenticate to POST /v3/action/user/authenticate
|
||||
export async function auth_ae_obj__user_id_user_auth_key({
|
||||
api_cfg,
|
||||
account_id,
|
||||
user_id,
|
||||
user_auth_key,
|
||||
params = {},
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
account_id: string;
|
||||
user_id: string;
|
||||
user_auth_key: string;
|
||||
params?: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
if (log_lvl) {
|
||||
@@ -447,61 +406,36 @@ export async function auth_ae_obj__user_id_user_auth_key({
|
||||
);
|
||||
}
|
||||
|
||||
const endpoint = '/user/authenticate';
|
||||
|
||||
// Prepare API config with correct headers to override global guest settings
|
||||
const use_api_cfg = { ...api_cfg, headers: { ...api_cfg.headers } };
|
||||
if (account_id) {
|
||||
use_api_cfg.headers['x-account-id'] = account_id;
|
||||
delete use_api_cfg.headers['x-no-account-id'];
|
||||
params['account_id'] = account_id;
|
||||
}
|
||||
|
||||
params['user_id'] = user_id; // Required
|
||||
params['auth_key'] = user_auth_key; // Required
|
||||
params['inc_jwt'] = true; // Request a JWT in the response
|
||||
if (log_lvl > 1) {
|
||||
console.log(`auth_ae_obj__user_id_user_auth_key() - params:`, params);
|
||||
}
|
||||
use_api_cfg.headers['x-account-id'] = account_id;
|
||||
delete use_api_cfg.headers['x-no-account-id'];
|
||||
|
||||
ae_promises.auth__user_id_user_key = await api
|
||||
.get_object({
|
||||
.post_object({
|
||||
api_cfg: use_api_cfg,
|
||||
endpoint: endpoint,
|
||||
params: params,
|
||||
log_lvl: log_lvl
|
||||
endpoint: '/v3/action/user/authenticate',
|
||||
// WHY: valid_email=true marks the user's email as verified on successful magic-link auth
|
||||
data: { user_id, auth_key: user_auth_key, valid_email: true },
|
||||
log_lvl
|
||||
})
|
||||
.then(async function (user_obj_get_result) {
|
||||
if (user_obj_get_result) {
|
||||
return user_obj_get_result;
|
||||
} else {
|
||||
console.log('No results returned.');
|
||||
return null;
|
||||
}
|
||||
.then(function (result: any) {
|
||||
return result ?? null;
|
||||
})
|
||||
.catch(function (error: any) {
|
||||
console.log('No results returned or failed.', error);
|
||||
console.log('auth_ae_obj__user_id_user_auth_key failed:', error);
|
||||
return null;
|
||||
});
|
||||
|
||||
if (log_lvl) {
|
||||
console.log(
|
||||
'ae_promises.auth__user_id_user_key:',
|
||||
ae_promises.auth__user_id_user_key
|
||||
);
|
||||
}
|
||||
return ae_promises.auth__user_id_user_key;
|
||||
}
|
||||
|
||||
// Send an email to the user with a new one time use authentication key.
|
||||
// Updated 2025-04-08
|
||||
// NOTE: This legacy endpoint is sensitive to extra query parameters and will 500 if account_id is passed in the URL.
|
||||
// Updated 2026-04-25 — migrated from GET /user/{id}/email_auth_key_url to V3 action path
|
||||
export async function send_email_auth_ae_obj__user_id({
|
||||
api_cfg,
|
||||
account_id,
|
||||
user_id,
|
||||
base_url,
|
||||
key_param_name = 'user_key', // API defaults to 'auth_key'
|
||||
params = {},
|
||||
key_param_name = 'user_key',
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
@@ -509,7 +443,6 @@ export async function send_email_auth_ae_obj__user_id({
|
||||
user_id: string;
|
||||
base_url?: string;
|
||||
key_param_name?: string;
|
||||
params?: key_val;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
if (log_lvl) {
|
||||
@@ -518,47 +451,30 @@ export async function send_email_auth_ae_obj__user_id({
|
||||
);
|
||||
}
|
||||
|
||||
const email_auth_key_endpoint = `/user/${user_id}/email_auth_key_url`;
|
||||
params = {
|
||||
root_url: base_url,
|
||||
key_param_name: key_param_name
|
||||
};
|
||||
|
||||
// Prepare API config with correct headers
|
||||
const use_api_cfg = { ...api_cfg, headers: { ...api_cfg.headers } };
|
||||
if (account_id) {
|
||||
use_api_cfg.headers['x-account-id'] = account_id;
|
||||
delete use_api_cfg.headers['x-no-account-id'];
|
||||
// WARNING: Do NOT add account_id to params here, as it causes a 500 error on the legacy backend.
|
||||
}
|
||||
use_api_cfg.headers['x-account-id'] = account_id;
|
||||
delete use_api_cfg.headers['x-no-account-id'];
|
||||
|
||||
ae_promises.auth_key__send_email = await api.get_object({
|
||||
api_cfg: use_api_cfg,
|
||||
endpoint: email_auth_key_endpoint,
|
||||
params: params,
|
||||
log_lvl: log_lvl
|
||||
endpoint: `/v3/action/user/${user_id}/email_auth_key_url`,
|
||||
params: { root_url: base_url, key_param_name },
|
||||
log_lvl
|
||||
});
|
||||
|
||||
return ae_promises.auth_key__send_email;
|
||||
}
|
||||
|
||||
// Look up user based on email address provided
|
||||
// Updated 2025-04-08
|
||||
// Updated 2026-04-25 — migrated from GET /user/lookup_email to POST /v3/crud/user/search
|
||||
export async function qry_ae_obj_li__user_email({
|
||||
api_cfg,
|
||||
account_id,
|
||||
null_account_id = false,
|
||||
email,
|
||||
params = {},
|
||||
try_cache = true,
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
account_id: string;
|
||||
null_account_id?: boolean;
|
||||
email: string;
|
||||
params?: key_val;
|
||||
try_cache?: boolean;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
if (log_lvl) {
|
||||
@@ -567,56 +483,39 @@ export async function qry_ae_obj_li__user_email({
|
||||
);
|
||||
}
|
||||
|
||||
const endpoint = '/user/lookup_email';
|
||||
|
||||
// Prepare API config with correct headers
|
||||
const use_api_cfg = { ...api_cfg, headers: { ...api_cfg.headers } };
|
||||
if (account_id) {
|
||||
use_api_cfg.headers['x-account-id'] = account_id;
|
||||
delete use_api_cfg.headers['x-no-account-id'];
|
||||
params['account_id'] = account_id;
|
||||
}
|
||||
use_api_cfg.headers['x-account-id'] = account_id;
|
||||
delete use_api_cfg.headers['x-no-account-id'];
|
||||
|
||||
params['email'] = email; // Required
|
||||
params['null_account_id'] = null_account_id || false;
|
||||
|
||||
ae_promises.qry__user_email = await api
|
||||
.get_object({
|
||||
const results = await api
|
||||
.search_ae_obj({
|
||||
api_cfg: use_api_cfg,
|
||||
endpoint: endpoint,
|
||||
params: params,
|
||||
log_lvl: log_lvl
|
||||
})
|
||||
.then(async function (user_obj_get_result) {
|
||||
if (user_obj_get_result) {
|
||||
return user_obj_get_result;
|
||||
} else {
|
||||
console.log('No results returned.');
|
||||
return null;
|
||||
}
|
||||
obj_type: 'user',
|
||||
search_query: { and: [{ field: 'email', op: 'eq', value: email }] },
|
||||
log_lvl
|
||||
})
|
||||
.catch(function (error: any) {
|
||||
console.log('No results returned or failed.', error);
|
||||
console.log('qry_ae_obj_li__user_email failed:', error);
|
||||
return null;
|
||||
});
|
||||
|
||||
return ae_promises.qry__user_email;
|
||||
// Return the first match to preserve the same interface callers expect
|
||||
// (the old /user/lookup_email endpoint returned a single user object)
|
||||
return results?.[0] ?? null;
|
||||
}
|
||||
|
||||
// Change user password
|
||||
// Updated 2025-04-11
|
||||
// Updated 2026-04-25 — migrated from PATCH /user/{id}/change_password to POST /v3/action
|
||||
export async function auth_ae_obj__user_id_change_password({
|
||||
api_cfg,
|
||||
account_id,
|
||||
user_id,
|
||||
password,
|
||||
params = {},
|
||||
log_lvl = 0
|
||||
}: {
|
||||
api_cfg: any;
|
||||
account_id: string;
|
||||
user_id: string;
|
||||
password: string;
|
||||
params?: key_val;
|
||||
log_lvl?: number;
|
||||
}) {
|
||||
if (log_lvl) {
|
||||
@@ -625,27 +524,19 @@ export async function auth_ae_obj__user_id_change_password({
|
||||
);
|
||||
}
|
||||
|
||||
const endpoint = `/user/${user_id}/change_password`;
|
||||
|
||||
params['user_id'] = user_id; // Required
|
||||
ae_promises.change_password__user_id = await api
|
||||
.patch_object({
|
||||
api_cfg: api_cfg,
|
||||
endpoint: endpoint,
|
||||
params: params,
|
||||
data: { password: password },
|
||||
log_lvl: log_lvl
|
||||
.post_object({
|
||||
api_cfg,
|
||||
endpoint: `/v3/action/user/${user_id}/change_password`,
|
||||
data: { new_password: password },
|
||||
log_lvl
|
||||
})
|
||||
.then(async function (change_password_result) {
|
||||
if (change_password_result) {
|
||||
return change_password_result;
|
||||
} else {
|
||||
console.log('No results returned.');
|
||||
return null;
|
||||
}
|
||||
.then(function (result: any) {
|
||||
return result ?? null;
|
||||
})
|
||||
.catch(function (error: any) {
|
||||
console.log('No results returned or failed.', error);
|
||||
console.log('auth_ae_obj__user_id_change_password failed:', error);
|
||||
return null;
|
||||
});
|
||||
|
||||
return ae_promises.change_password__user_id;
|
||||
|
||||
@@ -233,12 +233,18 @@ function handle_send_auth_email({ user_id }: { user_id: string }) {
|
||||
console.log($ae_loc.hostname); // URL hostname
|
||||
|
||||
// This function creates a new auth_key and then sends an email to the user with the new auth key.
|
||||
// WHY: root_url is required by the backend email builder — if null/undefined it
|
||||
// produces a broken link ("None?user_id=..."). Fall back to window.location.origin
|
||||
// in case $ae_loc.base_url is not yet set when this fires.
|
||||
const magic_link_root_url =
|
||||
$ae_loc.base_url || (browser ? window.location.origin : '');
|
||||
|
||||
ae_promises.send_email_auth_ae_obj__user_id =
|
||||
core_func.send_email_auth_ae_obj__user_id({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $slct.account_id,
|
||||
user_id: user_id,
|
||||
base_url: $ae_loc.base_url,
|
||||
base_url: magic_link_root_url,
|
||||
log_lvl: 0
|
||||
});
|
||||
}
|
||||
@@ -252,22 +258,13 @@ function handle_lookup_user_email({ email }: { email: string }) {
|
||||
.qry_ae_obj_li__user_email({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $slct.account_id,
|
||||
null_account_id: false,
|
||||
email: email,
|
||||
log_lvl: 0
|
||||
})
|
||||
.then((user_response) => {
|
||||
if (user_response?.user_id_random) {
|
||||
if (user_response?.user_id) {
|
||||
console.log(`User found for email:`, user_response);
|
||||
handle_send_auth_email({
|
||||
user_id: user_response.user_id_random
|
||||
});
|
||||
email_send_status = 'sent';
|
||||
} else if (user_response && user_response.length > 0) {
|
||||
console.log(`Multiple users found for email:`, user_response);
|
||||
handle_send_auth_email({
|
||||
user_id: user_response[0].user_id_random
|
||||
});
|
||||
handle_send_auth_email({ user_id: user_response.user_id });
|
||||
email_send_status = 'sent';
|
||||
} else {
|
||||
console.warn('No user found for email:', email);
|
||||
@@ -321,24 +318,16 @@ async function handle_change_password() {
|
||||
await core_func.qry_ae_obj_li__user_email({
|
||||
api_cfg: $ae_api,
|
||||
account_id: $slct.account_id,
|
||||
null_account_id: false,
|
||||
email: user_email,
|
||||
log_lvl: 0
|
||||
});
|
||||
|
||||
if (!ae_promises.load__user_obj_li) {
|
||||
// This means a 404 was returned
|
||||
if (!ae_promises.load__user_obj_li?.user_id) {
|
||||
alert('No user found with that email address.');
|
||||
return;
|
||||
} else if (ae_promises.load__user_obj_li?.user_id_random) {
|
||||
} else {
|
||||
console.log(`User found for email:`, ae_promises.load__user_obj_li);
|
||||
use_user_id = ae_promises.load__user_obj_li.user_id_random;
|
||||
} else if (ae_promises.load__user_obj_li.length > 0) {
|
||||
console.log(
|
||||
`Multiple users found for email:`,
|
||||
ae_promises.load__user_obj_li
|
||||
);
|
||||
use_user_id = ae_promises.load__user_obj_li[0].user_id_random;
|
||||
use_user_id = ae_promises.load__user_obj_li.user_id;
|
||||
}
|
||||
} else {
|
||||
wait_for_lookup = false;
|
||||
|
||||
Reference in New Issue
Block a user