fix(auth): migrate sign-in from legacy /user/* to V3 action endpoints
Legacy GET /user/authenticate and GET /user/lookup_email were returning 404
because the backend has removed those routes. Updated all 5 auth functions in
ae_core__user.ts to use V3 equivalents:
- auth_ae_obj__username_password: GET /user/authenticate → POST /v3/action/user/authenticate (body)
- auth_ae_obj__user_id_user_auth_key: GET /user/authenticate → POST /v3/action/user/authenticate (body)
- send_email_auth_ae_obj__user_id: GET /user/{id}/email_auth_key_url → GET /v3/action/user/{id}/email_auth_key_url
- qry_ae_obj_li__user_email: GET /user/lookup_email → POST /v3/crud/user/search
- auth_ae_obj__user_id_change_password: PATCH /user/{id}/change_password → POST /v3/action/user/{id}/change_password
Credentials are now in the POST body (not query params) for authenticate calls.
Updated two call sites in e_app_sign_in_out.svelte to drop removed null_account_id param.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -252,7 +252,6 @@ 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
|
||||
})
|
||||
@@ -321,7 +320,6 @@ 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
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user