[add] Funktionalität zur Passwort-Änderung
This commit is contained in:
parent
08442edb12
commit
aeda24c6fb
9 changed files with 355 additions and 7 deletions
63
source/api/actions/member_password_change_execute.ts
Normal file
63
source/api/actions/member_password_change_execute.ts
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
namespace _espe.api
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo ausgeklügelte Durchsatzratenbegrenzung
|
||||||
|
*/
|
||||||
|
export function register_member_password_change_execute(
|
||||||
|
rest_subject : lib_plankton.rest.type_rest
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
register<
|
||||||
|
{
|
||||||
|
token : string;
|
||||||
|
password_new : string;
|
||||||
|
},
|
||||||
|
null
|
||||||
|
>(
|
||||||
|
rest_subject,
|
||||||
|
lib_plankton.http.enum_method.patch,
|
||||||
|
"/member/password_change/execute/:id",
|
||||||
|
{
|
||||||
|
"description": "Führt eine Passwort-Änderung für ein Mitglied durch",
|
||||||
|
"input_schema": () => ({
|
||||||
|
"nullable": false,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"token": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"password_new": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
"description": "das neue Passwort"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"token",
|
||||||
|
"password_new",
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
"output_schema": () => ({
|
||||||
|
"nullable": true
|
||||||
|
}),
|
||||||
|
"restriction": restriction_none,
|
||||||
|
"execution": async ({"path_parameters": path_parameters, "input": input}) => {
|
||||||
|
const member_id : _espe.type.member_id = parseInt(path_parameters["id"]);
|
||||||
|
await _espe.service.member.password_change_execute(
|
||||||
|
member_id,
|
||||||
|
input.token,
|
||||||
|
input.password_new
|
||||||
|
);
|
||||||
|
return Promise.resolve({
|
||||||
|
"status_code": 200,
|
||||||
|
"data": null
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
60
source/api/actions/member_password_change_initialize.ts
Normal file
60
source/api/actions/member_password_change_initialize.ts
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
namespace _espe.api
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @todo ausgeklügelte Durchsatzratenbegrenzung
|
||||||
|
* @todo captcha
|
||||||
|
*/
|
||||||
|
export function register_member_password_change_initialize(
|
||||||
|
rest_subject : lib_plankton.rest.type_rest
|
||||||
|
) : void
|
||||||
|
{
|
||||||
|
register<
|
||||||
|
{
|
||||||
|
identifier : string;
|
||||||
|
url_template : string;
|
||||||
|
},
|
||||||
|
null
|
||||||
|
>(
|
||||||
|
rest_subject,
|
||||||
|
lib_plankton.http.enum_method.post,
|
||||||
|
"/member/password_change/initialize",
|
||||||
|
{
|
||||||
|
"description": "Versucht dem gegebenen Identifikator ein Mitglied zuzuordnen und sendet dem ermittelten Mitglied einen Passwort-Änderungs-Verweis an die hinterlegte private E-Mail-Adresse",
|
||||||
|
"input_schema": () => ({
|
||||||
|
"nullable": false,
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"identifier": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
"description": "Anmelde-Name oder persönliche E-Mail-Adresse des Mitglieds"
|
||||||
|
},
|
||||||
|
"url_template": {
|
||||||
|
"nullable": false,
|
||||||
|
"type": "string",
|
||||||
|
"description": "Schablone für URL; Platz-Halter: id,token"
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"additionalProperties": false,
|
||||||
|
"required": [
|
||||||
|
"identifier",
|
||||||
|
"url_template",
|
||||||
|
]
|
||||||
|
}),
|
||||||
|
"output_schema": () => ({
|
||||||
|
"nullable": true
|
||||||
|
}),
|
||||||
|
"restriction": restriction_none,
|
||||||
|
"execution": async ({"input": input}) => {
|
||||||
|
await _espe.service.member.password_change_initialize(input.identifier, input.url_template);
|
||||||
|
return Promise.resolve({
|
||||||
|
"status_code": 200,
|
||||||
|
"data": null
|
||||||
|
});
|
||||||
|
},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -33,6 +33,11 @@ namespace _espe.api
|
||||||
_espe.api.register_member_read(rest_subject);
|
_espe.api.register_member_read(rest_subject);
|
||||||
_espe.api.register_member_modify(rest_subject);
|
_espe.api.register_member_modify(rest_subject);
|
||||||
// _espe.api.register_member_delete(rest_subject);
|
// _espe.api.register_member_delete(rest_subject);
|
||||||
|
// password_change
|
||||||
|
{
|
||||||
|
_espe.api.register_member_password_change_initialize(rest_subject);
|
||||||
|
_espe.api.register_member_password_change_execute(rest_subject);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -90,6 +90,7 @@ namespace _espe.conf
|
||||||
};
|
};
|
||||||
settings : {
|
settings : {
|
||||||
target_domain : string;
|
target_domain : string;
|
||||||
|
frontend_url_base : (null | string);
|
||||||
prefix_for_numberbased_email_addresses : string;
|
prefix_for_numberbased_email_addresses : string;
|
||||||
registration_email : {
|
registration_email : {
|
||||||
subject : string;
|
subject : string;
|
||||||
|
@ -102,6 +103,17 @@ namespace _espe.conf
|
||||||
must_contain_number : boolean;
|
must_contain_number : boolean;
|
||||||
must_contain_special_character : boolean;
|
must_contain_special_character : boolean;
|
||||||
};
|
};
|
||||||
|
password_change : {
|
||||||
|
cooldown_time : int;
|
||||||
|
initialization_email : {
|
||||||
|
subject : string;
|
||||||
|
body : string;
|
||||||
|
};
|
||||||
|
execution_email : {
|
||||||
|
subject : string;
|
||||||
|
body : string;
|
||||||
|
};
|
||||||
|
};
|
||||||
name_index : {
|
name_index : {
|
||||||
veil : boolean;
|
veil : boolean;
|
||||||
salt : (null | string);
|
salt : (null | string);
|
||||||
|
@ -229,6 +241,7 @@ namespace _espe.conf
|
||||||
"settings": (
|
"settings": (
|
||||||
((node_settings) => ({
|
((node_settings) => ({
|
||||||
"target_domain": (node_settings["target_domain"] ?? "example.org"),
|
"target_domain": (node_settings["target_domain"] ?? "example.org"),
|
||||||
|
"frontend_url_base": (node_settings["frontend_url_base"] ?? null), // TODO: mandatory?
|
||||||
"prefix_for_numberbased_email_addresses": (node_settings["prefix_for_numberbased_email_addresses"] ?? "member-"),
|
"prefix_for_numberbased_email_addresses": (node_settings["prefix_for_numberbased_email_addresses"] ?? "member-"),
|
||||||
"registration_email": {
|
"registration_email": {
|
||||||
"subject": ((node_settings["registration_email"] ?? {})["subject"] ?? "Registration"),
|
"subject": ((node_settings["registration_email"] ?? {})["subject"] ?? "Registration"),
|
||||||
|
@ -251,6 +264,19 @@ namespace _espe.conf
|
||||||
"must_contain_special_character": (node_settings_password_policy["must_contain_special_character"] ?? true),
|
"must_contain_special_character": (node_settings_password_policy["must_contain_special_character"] ?? true),
|
||||||
})) (node_settings["password_policy"] ?? {})
|
})) (node_settings["password_policy"] ?? {})
|
||||||
),
|
),
|
||||||
|
"password_change": (
|
||||||
|
((node_settings_password_change) => ({
|
||||||
|
"cooldown_time": (node_settings_password_change["cooldown_time"] ?? 86400),
|
||||||
|
"initialization_email": {
|
||||||
|
"subject": ((node_settings_password_change["initialization_email"] ?? {})["subject"] ?? "Password change initialized"),
|
||||||
|
"body": ((node_settings_password_change["initialization_email"] ?? {})["body"] ?? "{{url}}"),
|
||||||
|
},
|
||||||
|
"execution_email": {
|
||||||
|
"subject": ((node_settings_password_change["execution_email"] ?? {})["subject"] ?? "Password changed"),
|
||||||
|
"body": ((node_settings_password_change["execution_email"] ?? {})["body"] ?? ""),
|
||||||
|
},
|
||||||
|
})) (node_settings["password_change"] ?? {})
|
||||||
|
),
|
||||||
"name_index": (
|
"name_index": (
|
||||||
((node_settings_password_policy) => ({
|
((node_settings_password_policy) => ({
|
||||||
"veil": (node_settings_password_policy["veil"] ?? false),
|
"veil": (node_settings_password_policy["veil"] ?? false),
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace _espe.database
|
||||||
/**
|
/**
|
||||||
*/
|
*/
|
||||||
const _compatible_revisions : Array<string> = [
|
const _compatible_revisions : Array<string> = [
|
||||||
"r2",
|
"r3",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -61,6 +61,8 @@ namespace _espe.repository.member
|
||||||
"email_redirect_to_private_address": (object.email_redirect_to_private_address ? 1 : 0),
|
"email_redirect_to_private_address": (object.email_redirect_to_private_address ? 1 : 0),
|
||||||
"email_allow_sending": (object.email_allow_sending ? 1 : 0),
|
"email_allow_sending": (object.email_allow_sending ? 1 : 0),
|
||||||
"password_image": object.password_image,
|
"password_image": object.password_image,
|
||||||
|
"password_change_last_attempt": object.password_change_last_attempt,
|
||||||
|
"password_change_token": object.password_change_token,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,6 +85,8 @@ namespace _espe.repository.member
|
||||||
"email_redirect_to_private_address": (row["email_redirect_to_private_address"] > 0),
|
"email_redirect_to_private_address": (row["email_redirect_to_private_address"] > 0),
|
||||||
"email_allow_sending": (row["email_allow_sending"] > 0),
|
"email_allow_sending": (row["email_allow_sending"] > 0),
|
||||||
"password_image": row["password_image"],
|
"password_image": row["password_image"],
|
||||||
|
"password_change_last_attempt": row["password_change_last_attempt"],
|
||||||
|
"password_change_token": row["password_change_token"],
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,6 +207,24 @@ namespace _espe.service.member
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ermittelt das Passwort-Abbild anhand des tatsächlichen Passworts
|
||||||
|
*/
|
||||||
|
function password_image(
|
||||||
|
password : (null | string)
|
||||||
|
) : Promise<string>
|
||||||
|
{
|
||||||
|
return (
|
||||||
|
(
|
||||||
|
(! (password === null))
|
||||||
|
&&
|
||||||
|
(! (password === ""))
|
||||||
|
)
|
||||||
|
? _espe.helpers.bcrypt_compute(password)
|
||||||
|
: null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* gibt die vollständigen Daten aller Mitglieder aus
|
* gibt die vollständigen Daten aller Mitglieder aus
|
||||||
*/
|
*/
|
||||||
|
@ -282,6 +300,8 @@ namespace _espe.service.member
|
||||||
"email_redirect_to_private_address": false,
|
"email_redirect_to_private_address": false,
|
||||||
"email_allow_sending": false,
|
"email_allow_sending": false,
|
||||||
"password_image": null,
|
"password_image": null,
|
||||||
|
"password_change_last_attempt": null,
|
||||||
|
"password_change_token": null,
|
||||||
};
|
};
|
||||||
const id : _espe.type.member_id = await _espe.repository.member.create(object);
|
const id : _espe.type.member_id = await _espe.repository.member.create(object);
|
||||||
notify_change();
|
notify_change();
|
||||||
|
@ -291,6 +311,8 @@ namespace _espe.service.member
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sendet an ein Mitglied eine E-Mail mit Aufforderung zur Registrierung
|
* sendet an ein Mitglied eine E-Mail mit Aufforderung zur Registrierung
|
||||||
|
*
|
||||||
|
* @todo conf:settings.frontend_url_base verwenden
|
||||||
*/
|
*/
|
||||||
export async function summon(
|
export async function summon(
|
||||||
member_id : _espe.type.member_id,
|
member_id : _espe.type.member_id,
|
||||||
|
@ -412,11 +434,7 @@ namespace _espe.service.member
|
||||||
member_object.email_use_veiled_address = data.email_use_veiled_address
|
member_object.email_use_veiled_address = data.email_use_veiled_address
|
||||||
member_object.email_use_nominal_address = data.email_use_nominal_address
|
member_object.email_use_nominal_address = data.email_use_nominal_address
|
||||||
member_object.email_redirect_to_private_address = data.email_redirect_to_private_address;
|
member_object.email_redirect_to_private_address = data.email_redirect_to_private_address;
|
||||||
member_object.password_image = (
|
member_object.password_image = await password_image(data.password);
|
||||||
password_set
|
|
||||||
? await _espe.helpers.bcrypt_compute(data.password)
|
|
||||||
: null
|
|
||||||
);
|
|
||||||
member_object.registered = true;
|
member_object.registered = true;
|
||||||
await _espe.repository.member.update(member_id, member_object);
|
await _espe.repository.member.update(member_id, member_object);
|
||||||
|
|
||||||
|
@ -432,7 +450,7 @@ namespace _espe.service.member
|
||||||
.map(admin => admin.email_address)
|
.map(admin => admin.email_address)
|
||||||
.filter(x => (x !== null))
|
.filter(x => (x !== null))
|
||||||
),
|
),
|
||||||
"Eingegangene Registrierung",
|
"Eingegangene Registrierung", // TODO
|
||||||
("Member-ID: " + member_id.toFixed(0))
|
("Member-ID: " + member_id.toFixed(0))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -467,12 +485,180 @@ namespace _espe.service.member
|
||||||
"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
|
"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
|
||||||
"email_allow_sending": member_object_old.email_allow_sending,
|
"email_allow_sending": member_object_old.email_allow_sending,
|
||||||
"password_image": member_object_old.password_image,
|
"password_image": member_object_old.password_image,
|
||||||
|
"password_change_last_attempt": member_object_old.password_change_last_attempt,
|
||||||
|
"password_change_token": member_object_old.password_change_token,
|
||||||
};
|
};
|
||||||
await _espe.repository.member.update(member_id, member_object_new);
|
await _espe.repository.member.update(member_id, member_object_new);
|
||||||
notify_change();
|
notify_change();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bereitet eine Passwort-Rücksetzung für Mitglieder vor
|
||||||
|
*
|
||||||
|
* @todo Zwangs-Pause falls Abklingzeit noch nicht vorbei um vorzugaukeln, dass es geklappt hat?
|
||||||
|
*/
|
||||||
|
export async function password_change_initialize(
|
||||||
|
identifier : string,
|
||||||
|
url_template : string
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
if (_espe.conf.get().settings.frontend_url_base === null) {
|
||||||
|
return Promise.reject<void>(new Error("no frontend url base set; add in conf as 'settings.frontend_url_base'!"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const now : int = lib_plankton.base.get_current_timestamp(true);
|
||||||
|
const cooldown_time : int = _espe.conf.get().settings.password_change.cooldown_time;
|
||||||
|
const member_ids : Array<_espe.type.member_id> = await (
|
||||||
|
(await _espe.repository.member.dump())
|
||||||
|
.filter(
|
||||||
|
member_entry => (
|
||||||
|
(
|
||||||
|
(! (member_entry.object.email_address_private === null))
|
||||||
|
&&
|
||||||
|
(member_entry.object.email_address_private === identifier)
|
||||||
|
)
|
||||||
|
||
|
||||||
|
(name_login(member_entry.object) === identifier)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
member_entry => member_entry.id
|
||||||
|
)
|
||||||
|
);
|
||||||
|
for await (const member_id of member_ids) {
|
||||||
|
const member_object_old : _espe.type.member_object = await _espe.repository.member.read(member_id);
|
||||||
|
if (
|
||||||
|
(! (member_object_old.password_change_last_attempt === null))
|
||||||
|
&&
|
||||||
|
((now - member_object_old.password_change_last_attempt) <= cooldown_time)
|
||||||
|
) {
|
||||||
|
lib_plankton.log.notice(
|
||||||
|
"member_password_change_cooldown_not_over",
|
||||||
|
{
|
||||||
|
"member_id": member_id,
|
||||||
|
"last_attempt": member_object_old.password_change_last_attempt,
|
||||||
|
"now": now,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (member_object_old.email_address_private === null) {
|
||||||
|
lib_plankton.log.notice(
|
||||||
|
"member_password_change_impossible_due_to_missing_private_email_address",
|
||||||
|
{
|
||||||
|
"member_id": member_id,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// keine echte Verifizierung, der Algorithmus ist aber der passende
|
||||||
|
const token : string = await _espe.helpers.verification_get(Math.floor(Math.random() * (1 << 24)));
|
||||||
|
const member_object_new : _espe.type.member_object = {
|
||||||
|
"membership_number": member_object_old.membership_number,
|
||||||
|
"name_real_value": member_object_old.name_real_value,
|
||||||
|
"name_real_index": member_object_old.name_real_index,
|
||||||
|
"email_address_private": member_object_old.email_address_private,
|
||||||
|
"registered": member_object_old.registered,
|
||||||
|
"enabled": member_object_old.enabled,
|
||||||
|
"email_use_veiled_address": member_object_old.email_use_veiled_address,
|
||||||
|
"email_use_nominal_address": member_object_old.email_use_nominal_address,
|
||||||
|
"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
|
||||||
|
"email_allow_sending": member_object_old.email_allow_sending,
|
||||||
|
"password_image": member_object_old.password_image,
|
||||||
|
"password_change_last_attempt": now,
|
||||||
|
"password_change_token": token,
|
||||||
|
};
|
||||||
|
await _espe.repository.member.update(member_id, member_object_new);
|
||||||
|
// notify_change();
|
||||||
|
await _espe.helpers.email_send(
|
||||||
|
[
|
||||||
|
member_object_old.email_address_private,
|
||||||
|
],
|
||||||
|
_espe.conf.get().settings.password_change.initialization_email.subject,
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
_espe.conf.get().settings.password_change.initialization_email.body,
|
||||||
|
{
|
||||||
|
"name": member_object_old.name_real_value,
|
||||||
|
"url": lib_plankton.string.coin(
|
||||||
|
"{{base}}{{rest}}",
|
||||||
|
{
|
||||||
|
"base": _espe.conf.get().settings.frontend_url_base,
|
||||||
|
"rest": lib_plankton.string.coin(
|
||||||
|
url_template,
|
||||||
|
{
|
||||||
|
"id": member_id.toFixed(0),
|
||||||
|
"token": token,
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* führt eine Passwort-Rücksetzung für ein Mitglied durch
|
||||||
|
*
|
||||||
|
* @todo zeitliche Begrenzung?
|
||||||
|
*/
|
||||||
|
export async function password_change_execute(
|
||||||
|
member_id : _espe.type.member_id,
|
||||||
|
token : string,
|
||||||
|
password_new : string
|
||||||
|
) : Promise<void>
|
||||||
|
{
|
||||||
|
const member_object_old : _espe.type.member_object = await _espe.repository.member.read(member_id);
|
||||||
|
if (! (token === member_object_old.password_change_token)) {
|
||||||
|
lib_plankton.log.notice(
|
||||||
|
"member_password_change_token_invalid",
|
||||||
|
{
|
||||||
|
"member_id": member_id,
|
||||||
|
"token_sent": token,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
throw (new Error("password change token is invalid"));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const member_object_new : _espe.type.member_object = {
|
||||||
|
"membership_number": member_object_old.membership_number,
|
||||||
|
"name_real_value": member_object_old.name_real_value,
|
||||||
|
"name_real_index": member_object_old.name_real_index,
|
||||||
|
"email_address_private": member_object_old.email_address_private,
|
||||||
|
"registered": member_object_old.registered,
|
||||||
|
"enabled": member_object_old.enabled,
|
||||||
|
"email_use_veiled_address": member_object_old.email_use_veiled_address,
|
||||||
|
"email_use_nominal_address": member_object_old.email_use_nominal_address,
|
||||||
|
"email_redirect_to_private_address": member_object_old.email_redirect_to_private_address,
|
||||||
|
"email_allow_sending": member_object_old.email_allow_sending,
|
||||||
|
"password_image": await password_image(password_new),
|
||||||
|
"password_change_last_attempt": member_object_old.password_change_last_attempt,
|
||||||
|
"password_change_token": null,
|
||||||
|
};
|
||||||
|
await _espe.repository.member.update(member_id, member_object_new);
|
||||||
|
notify_change();
|
||||||
|
await _espe.helpers.email_send(
|
||||||
|
[
|
||||||
|
member_object_old.email_address_private,
|
||||||
|
],
|
||||||
|
_espe.conf.get().settings.password_change.execution_email.subject,
|
||||||
|
lib_plankton.string.coin(
|
||||||
|
_espe.conf.get().settings.password_change.execution_email.body,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
export async function remove(
|
export async function remove(
|
||||||
|
|
|
@ -20,6 +20,8 @@ namespace _espe.type
|
||||||
email_redirect_to_private_address : boolean;
|
email_redirect_to_private_address : boolean;
|
||||||
email_allow_sending : boolean;
|
email_allow_sending : boolean;
|
||||||
password_image : (null | string);
|
password_image : (null | string);
|
||||||
|
password_change_last_attempt : (null | int);
|
||||||
|
password_change_token : (null | string);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,8 @@ ${dir_temp}/espe-core.js ${dir_temp}/espe-core.d.ts: \
|
||||||
${dir_source}/api/actions/member_list.ts \
|
${dir_source}/api/actions/member_list.ts \
|
||||||
${dir_source}/api/actions/member_read.ts \
|
${dir_source}/api/actions/member_read.ts \
|
||||||
${dir_source}/api/actions/member_modify.ts \
|
${dir_source}/api/actions/member_modify.ts \
|
||||||
|
${dir_source}/api/actions/member_password_change_initialize.ts \
|
||||||
|
${dir_source}/api/actions/member_password_change_execute.ts \
|
||||||
${dir_source}/api/functions.ts \
|
${dir_source}/api/functions.ts \
|
||||||
${dir_source}/conf.ts
|
${dir_source}/conf.ts
|
||||||
@ ${cmd_log} "compile | core …"
|
@ ${cmd_log} "compile | core …"
|
||||||
|
|
Loading…
Add table
Reference in a new issue