@@ -13,6 +13,12 @@ import {NTBBLadder} from './ladder';
13
13
import { Replays , md5 } from './replays' ;
14
14
import { toID } from './server' ;
15
15
import * as tables from './tables' ;
16
+ import * as nodemailer from 'nodemailer' ;
17
+
18
+ // eslint-disable-next-line
19
+ const EMAIL_REGEX = / (?: [ a - z 0 - 9 ! # $ % & \' * + \/ = ? ^ _ ` { | } ~ - ] + (?: \. [ a - z 0 - 9 ! # $ % & \' * + \/ = ? ^ _ ` { | } ~ - ] + ) * | " (?: [ \x01 - \x08 \x0b \x0c \x0e - \x1f \x21 \x23 - \x5b \x5d - \x7f ] | \\ [ \x01 - \x09 \x0b \x0c \x0e - \x7f ] ) * " ) @ (?: (?: [ a - z 0 - 9 ] (?: [ a - z 0 - 9 - ] * [ a - z 0 - 9 ] ) ? \. ) + [ a - z 0 - 9 ] (?: [ a - z 0 - 9 - ] * [ a - z 0 - 9 ] ) ? | \[ (?: (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? ) \. ) { 3 } (?: 2 5 [ 0 - 5 ] | 2 [ 0 - 4 ] [ 0 - 9 ] | [ 0 1 ] ? [ 0 - 9 ] [ 0 - 9 ] ? | [ a - z 0 - 9 - ] * [ a - z 0 - 9 ] : (?: [ \x01 - \x08 \x0b \x0c \x0e - \x1f \x21 - \x5a \x53 - \x7f ] | \\ [ \x01 - \x09 \x0b \x0c \x0e - \x7f ] ) + ) \] ) / i;
20
+
21
+ const mailer = nodemailer . createTransport ( Config . passwordemails . transportOpts ) ;
16
22
17
23
// shamelessly stolen from PS main
18
24
function bash ( command : string , cwd ?: string ) : Promise < [ number , string , string ] > {
@@ -416,4 +422,86 @@ export const actions: {[k: string]: QueryHandler} = {
416
422
if ( stderr ) throw new ActionError ( stderr ) ;
417
423
return { updated : update , success : true } ;
418
424
} ,
425
+ async setemail ( params ) {
426
+ if ( ! this . user . loggedin ) {
427
+ throw new ActionError ( `You must be logged in to set an email.` ) ;
428
+ }
429
+ if ( ! params . email || typeof params . email !== 'string' ) {
430
+ throw new ActionError ( `You must send an email.` ) ;
431
+ }
432
+ const email = EMAIL_REGEX . exec ( params . email ) ?. [ 0 ] ;
433
+ if ( ! email ) throw new ActionError ( `Invalid email sent.` ) ;
434
+ const data = await tables . users . get ( '*' , this . user . id ) ;
435
+ if ( ! data ) throw new ActionError ( `You are not registered.` ) ;
436
+ if ( data . email ?. endsWith ( '@' ) ) {
437
+ throw new ActionError ( `You have 2FA, and do not need to set an email for password resets.` ) ;
438
+ }
439
+ const result = await tables . users . update ( this . user . id , { email} ) ;
440
+
441
+ delete ( data as any ) . passwordhash ;
442
+ return {
443
+ success : ! ! result . changedRows ,
444
+ curuser : Object . assign ( data , { email} ) ,
445
+ } ;
446
+ } ,
447
+ async clearemail ( ) {
448
+ if ( ! this . user . loggedin ) {
449
+ throw new ActionError ( `You must be logged in to edit your email.` ) ;
450
+ }
451
+ const data = await tables . users . get ( '*' , this . user . id ) ;
452
+ if ( ! data ) throw new ActionError ( `You are not registered.` ) ;
453
+ if ( data . email ?. endsWith ( '@' ) ) {
454
+ throw new ActionError (
455
+ `You have 2FA, and need an administrator to set/unset your email manually.`
456
+ ) ;
457
+ }
458
+ const result = await tables . users . update ( this . user . id , { email : null } ) ;
459
+
460
+ delete ( data as any ) . passwordhash ;
461
+ return {
462
+ actionsuccess : ! ! result . changedRows ,
463
+ curuser : Object . assign ( data , { email : null } ) ,
464
+ } ;
465
+ } ,
466
+ async resetpassword ( params ) {
467
+ if ( typeof params . email !== 'string' || ! params . email ) {
468
+ throw new ActionError ( `You must provide an email address.` ) ;
469
+ }
470
+ const email = EMAIL_REGEX . exec ( params . email ) ?. [ 0 ] ;
471
+ if ( ! email ) {
472
+ throw new ActionError ( `Invalid email sent.` ) ;
473
+ }
474
+ const data = await tables . users . selectOne ( '*' , SQL `email = ${ email } ` ) ;
475
+ if ( ! data ) {
476
+ // no user associated with that email.
477
+ // ...pretend like it succeeded (we don't wanna leak that it's in use, after all)
478
+ return { success : true } ;
479
+ }
480
+ if ( ! data . email ) {
481
+ // should literally never happen
482
+ throw new Error ( `Account data found with no email, but had an email match` ) ;
483
+ }
484
+ if ( data . email . endsWith ( '@' ) ) {
485
+ throw new ActionError ( `You have 2FA, and so do not need a password reset.` ) ;
486
+ }
487
+ const token = await this . session . createPasswordResetToken ( data . username ) ;
488
+
489
+ await mailer . sendMail ( {
490
+ from : Config . passwordemails . from ,
491
+ to : data . email ,
492
+ subject : "Pokemon Showdown account password reset" ,
493
+ text : (
494
+ `You requested a password reset for the Pokemon Showdown account ${ data . userid } . Click this link https://${ Config . routes . root } /resetpassword/${ token } and follow the instructions to change your password.\n` +
495
+ `Not you? Please contact staff by typing /ht in any chatroom on Pokemon Showdown. \n` +
496
+ `If you are unable to do so, visit the Help chatroom.`
497
+ ) ,
498
+ html : (
499
+ `You requested a password reset for the Pokemon Showdown account ${ data . userid } . ` +
500
+ `Click <a href="https://${ Config . routes . root } /resetpassword/${ token } ">this link</a> and follow the instructions to change your password.<br />` +
501
+ `Not you? Please contact staff by typing <code>/ht</code> in any chatroom on Pokemon Showdown. <br />` +
502
+ `If you are unable to do so, visit the <a href="${ Config . routes . client } /help">Help</a> chatroom.`
503
+ ) ,
504
+ } ) ;
505
+ return { success : true } ;
506
+ } ,
419
507
} ;
0 commit comments