2019-05-09 18:54:39 +12:00
< ? php
2019-08-11 17:18:10 +12:00
global $utopia , $register , $request , $response , $user , $audit , $webhook , $project , $domain , $projectDB , $providers , $clients ;
2019-05-09 18:54:39 +12:00
use Utopia\Exception ;
use Utopia\Validator\WhiteList ;
use Utopia\Validator\Text ;
use Utopia\Validator\Email ;
use Utopia\Validator\Host ;
use Utopia\Validator\URL ;
use Utopia\Locale\Locale ;
use Auth\Auth ;
use Auth\Validator\Password ;
use Database\Database ;
use Database\Document ;
use Database\Validator\Authorization ;
use Database\Validator\UID ;
use Template\Template ;
use OpenSSL\OpenSSL ;
$utopia -> post ( '/v1/auth/register' )
-> desc ( 'Register User' )
-> label ( 'webhook' , 'auth.register' )
-> label ( 'scope' , 'auth' )
-> label ( 'sdk.namespace' , 'auth' )
-> label ( 'sdk.method' , 'register' )
2019-10-07 09:10:52 +13:00
-> label ( 'sdk.description' , '/docs/refernces/register.md' )
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.cookies' , true )
-> label ( 'abuse-limit' , 10 )
2019-10-07 09:10:52 +13:00
-> param ( 'email' , '' , function () { return new Email (); }, 'Account email' )
-> param ( 'password' , '' , function () { return new Password (); }, 'User password' )
-> param ( 'confirm' , '' , function () use ( $clients ) { return new Host ( $clients ); }, 'Confirmation URL to redirect user after confirm token has been sent to user email' ) // TODO add our own built-in confirm page
-> param ( 'success' , null , function () use ( $clients ) { return new Host ( $clients ); }, 'Redirect when registration succeed' , true )
-> param ( 'failure' , null , function () use ( $clients ) { return new Host ( $clients ); }, 'Redirect when registration failed' , true )
-> param ( 'name' , '' , function () { return new Text ( 100 ); }, 'User name' , true )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-29 11:55:54 +13:00
function ( $email , $password , $confirm , $success , $failure , $name ) use ( $request , $response , $register , $audit , $projectDB , $project , $webhook ) {
2019-09-30 19:13:40 +13:00
if ( 'console' === $project -> getUid ()) {
2019-10-02 05:11:51 +13:00
$whitlistEmails = $project -> getAttribute ( 'authWhitelistEmails' );
$whitlistIPs = $project -> getAttribute ( 'authWhitelistIPs' );
$whitlistDomains = $project -> getAttribute ( 'authWhitelistDomains' );
2019-09-13 04:20:51 +12:00
2019-09-30 19:13:40 +13:00
if ( ! empty ( $whitlistEmails ) && ! in_array ( $email , $whitlistEmails )) {
2019-09-13 19:13:30 +12:00
throw new Exception ( 'Console registration is restricted to specific emails. Contact your administrator for more information.' , 401 );
2019-09-13 04:20:51 +12:00
}
2019-09-30 19:13:40 +13:00
if ( ! empty ( $whitlistIPs ) && ! in_array ( $request -> getIP (), $whitlistIPs )) {
2019-09-13 19:13:30 +12:00
throw new Exception ( 'Console registration is restricted to specific IPs. Contact your administrator for more information.' , 401 );
2019-09-13 04:20:51 +12:00
}
2019-10-02 05:11:51 +13:00
if ( ! empty ( $whitlistDomains ) && ! in_array ( substr ( strrchr ( $email , '@' ), 1 ), $whitlistDomains )) {
2019-09-13 19:13:30 +12:00
throw new Exception ( 'Console registration is restricted to specific domains. Contact your administrator for more information.' , 401 );
2019-09-13 04:20:51 +12:00
}
}
2019-10-02 05:11:51 +13:00
2019-05-09 18:54:39 +12:00
$profile = $projectDB -> getCollection ([ // Get user by email address
'limit' => 1 ,
'first' => true ,
'filters' => [
2019-09-07 05:10:41 +12:00
'$collection=' . Database :: SYSTEM_COLLECTION_USERS ,
'email=' . $email ,
],
2019-05-09 18:54:39 +12:00
]);
2019-09-07 05:10:41 +12:00
if ( ! empty ( $profile )) {
if ( $failure ) {
2019-10-02 05:11:51 +13:00
$response -> redirect ( $failure . '?message=User already registered' );
2019-09-07 05:10:41 +12:00
2019-09-16 04:50:57 +12:00
return ;
2019-05-09 18:54:39 +12:00
}
throw new Exception ( 'User already registered' , 400 );
}
$expiry = time () + Auth :: TOKEN_EXPIRATION_LOGIN_LONG ;
$confirmSecret = Auth :: tokenGenerator ();
$loginSecret = Auth :: tokenGenerator ();
Authorization :: disable ();
$user = $projectDB -> createDocument ([
'$collection' => Database :: SYSTEM_COLLECTION_USERS ,
'$permissions' => [
'read' => [ '*' ],
'write' => [ 'user:{self}' ],
],
'email' => $email ,
'status' => Auth :: USER_STATUS_UNACTIVATED ,
'password' => Auth :: passwordHash ( $password ),
'password-update' => time (),
'registration' => time (),
'confirm' => false ,
'reset' => false ,
'name' => $name ,
]);
Authorization :: enable ();
2019-09-07 05:10:41 +12:00
if ( false === $user ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed saving user to DB' , 500 );
}
2019-09-07 05:10:41 +12:00
Authorization :: setRole ( 'user:' . $user -> getUid ());
2019-05-09 18:54:39 +12:00
$user
-> setAttribute ( 'tokens' , new Document ([
'$collection' => Database :: SYSTEM_COLLECTION_TOKENS ,
2019-09-07 05:10:41 +12:00
'$permissions' => [ 'read' => [ 'user:' . $user -> getUid ()], 'write' => [ 'user:' . $user -> getUid ()]],
2019-05-09 18:54:39 +12:00
'type' => Auth :: TOKEN_TYPE_CONFIRM ,
'secret' => Auth :: hash ( $confirmSecret ), // On way hash encryption to protect DB leak
'expire' => time () + Auth :: TOKEN_EXPIRATION_CONFIRM ,
'userAgent' => $request -> getServer ( 'HTTP_USER_AGENT' , 'UNKNOWN' ),
'ip' => $request -> getIP (),
2019-09-07 05:10:41 +12:00
]), Document :: SET_TYPE_APPEND )
2019-05-09 18:54:39 +12:00
-> setAttribute ( 'tokens' , new Document ([
'$collection' => Database :: SYSTEM_COLLECTION_TOKENS ,
2019-09-07 05:10:41 +12:00
'$permissions' => [ 'read' => [ 'user:' . $user -> getUid ()], 'write' => [ 'user:' . $user -> getUid ()]],
2019-05-09 18:54:39 +12:00
'type' => Auth :: TOKEN_TYPE_LOGIN ,
'secret' => Auth :: hash ( $loginSecret ), // On way hash encryption to protect DB leak
'expire' => $expiry ,
'userAgent' => $request -> getServer ( 'HTTP_USER_AGENT' , 'UNKNOWN' ),
'ip' => $request -> getIP (),
2019-09-07 05:10:41 +12:00
]), Document :: SET_TYPE_APPEND )
2019-05-09 18:54:39 +12:00
;
$user = $projectDB -> createDocument ( $user -> getArrayCopy ());
2019-09-07 05:10:41 +12:00
if ( false === $user ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed saving tokens to DB' , 500 );
}
// Send email address confirmation email
2019-09-29 11:55:54 +13:00
$confirm = Template :: parseURL ( $confirm );
$confirm [ 'query' ] = Template :: mergeQuery ((( isset ( $confirm [ 'query' ])) ? $confirm [ 'query' ] : '' ), [ 'userId' => $user -> getUid (), 'token' => $confirmSecret ]);
$confirm = Template :: unParseURL ( $confirm );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
$body = new Template ( __DIR__ . '/../config/locale/templates/' . Locale :: getText ( 'auth.emails.confirm.body' ));
2019-05-09 18:54:39 +12:00
$body
-> setParam ( '{{direction}}' , Locale :: getText ( 'settings.direction' ))
-> setParam ( '{{project}}' , $project -> getAttribute ( 'name' , [ '[APP-NAME]' ]))
-> setParam ( '{{name}}' , $name )
2019-09-29 11:55:54 +13:00
-> setParam ( '{{redirect}}' , $confirm )
2019-05-09 18:54:39 +12:00
;
2019-08-31 18:02:21 +12:00
$mail = $register -> get ( 'smtp' ); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */
2019-05-09 18:54:39 +12:00
2019-08-09 09:49:46 +12:00
$mail -> addAddress ( $email , $name );
$mail -> Subject = Locale :: getText ( 'auth.emails.confirm.title' );
2019-09-07 05:10:41 +12:00
$mail -> Body = $body -> render ();
2019-08-09 09:49:46 +12:00
$mail -> AltBody = strip_tags ( $body -> render ());
2019-05-09 18:54:39 +12:00
2019-08-09 09:49:46 +12:00
try {
$mail -> send ();
2019-09-07 05:10:41 +12:00
} catch ( \Exception $error ) {
2019-08-25 07:47:50 +12:00
// if($failure) {
// $response->redirect($failure);
// return;
// }
2019-05-09 18:54:39 +12:00
2019-08-25 17:16:17 +12:00
// throw new Exception('Problem sending mail: ' . $error->getError(), 500);
2019-05-09 18:54:39 +12:00
}
$webhook
-> setParam ( 'payload' , [
'name' => $name ,
'email' => $email ,
])
;
$audit
-> setParam ( 'userId' , $user -> getUid ())
-> setParam ( 'event' , 'auth.register' )
;
2019-08-24 19:30:12 +12:00
$response -> addCookie ( Auth :: $cookieName , Auth :: encodeSession ( $user -> getUid (), $loginSecret ), $expiry , '/' , COOKIE_DOMAIN , ( 'https' == $request -> getServer ( 'REQUEST_SCHEME' , 'https' )), true );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
if ( $success ) {
2019-05-09 18:54:39 +12:00
$response -> redirect ( $success );
}
$response -> json ( array ( 'result' => 'success' ));
}
);
$utopia -> post ( '/v1/auth/register/confirm' )
-> desc ( 'Confirm User' )
-> label ( 'webhook' , 'auth.confirm' )
-> label ( 'scope' , 'public' )
-> label ( 'sdk.namespace' , 'auth' )
-> label ( 'sdk.method' , 'confirm' )
2019-10-07 09:10:52 +13:00
-> label ( 'sdk.description' , '/docs/refernces/confirm.md' )
2019-05-09 18:54:39 +12:00
-> label ( 'abuse-limit' , 10 )
-> label ( 'abuse-key' , 'url:{url},userId:{param-userId}' )
2019-10-07 09:10:52 +13:00
-> param ( 'userId' , '' , function () { return new UID (); }, 'User unique ID' )
-> param ( 'token' , '' , function () { return new Text ( 256 ); }, 'Confirmation secret token' )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-07 05:10:41 +12:00
function ( $userId , $token ) use ( $response , $request , $projectDB , $audit ) {
2019-05-09 18:54:39 +12:00
$profile = $projectDB -> getCollection ([ // Get user by email address
'limit' => 1 ,
'first' => true ,
'filters' => [
2019-09-07 05:10:41 +12:00
'$collection=' . Database :: SYSTEM_COLLECTION_USERS ,
'$uid=' . $userId ,
],
2019-05-09 18:54:39 +12:00
]);
2019-09-07 05:10:41 +12:00
if ( empty ( $profile )) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'User not found' , 404 ); // TODO maybe hide this
}
$token = Auth :: tokenVerify ( $profile -> getAttribute ( 'tokens' , []), Auth :: TOKEN_TYPE_CONFIRM , $token );
2019-09-07 05:10:41 +12:00
if ( ! $token ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Confirmation token is not valid' , 401 );
}
$profile = $projectDB -> updateDocument ( array_merge ( $profile -> getArrayCopy (), [
'status' => Auth :: USER_STATUS_ACTIVATED ,
'confirm' => true ,
]));
2019-09-07 05:10:41 +12:00
if ( false === $profile ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed saving user to DB' , 500 );
}
2019-09-07 05:10:41 +12:00
if ( ! $projectDB -> deleteDocument ( $token )) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed to remove token from DB' , 500 );
}
$audit -> setParam ( 'event' , 'auth.confirm' );
$response -> json ( array ( 'result' => 'success' ));
}
);
$utopia -> post ( '/v1/auth/register/confirm/resend' )
-> desc ( 'Resend Confirmation' )
-> label ( 'scope' , 'account' )
-> label ( 'sdk.namespace' , 'auth' )
-> label ( 'sdk.method' , 'confirmResend' )
2019-10-07 09:10:52 +13:00
-> label ( 'sdk.description' , '/docs/refernces/confirm-resend.md' )
2019-05-09 18:54:39 +12:00
-> label ( 'abuse-limit' , 10 )
-> label ( 'abuse-key' , 'url:{url},userId:{param-userId}' )
2019-10-07 09:10:52 +13:00
-> param ( 'confirm' , '' , function () use ( $clients ) { return new Host ( $clients ); }, 'Confirmation URL to redirect user to your app after confirm token has been sent to user email.' )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-29 11:55:54 +13:00
function ( $confirm ) use ( $response , $request , $projectDB , $user , $register , $project ) {
2019-09-07 05:10:41 +12:00
if ( $user -> getAttribute ( 'confirm' , false )) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Email address is already confirmed' , 400 );
}
$secret = Auth :: tokenGenerator ();
$user -> setAttribute ( 'tokens' , new Document ([
'$collection' => Database :: SYSTEM_COLLECTION_TOKENS ,
2019-09-07 05:10:41 +12:00
'$permissions' => [ 'read' => [ 'user:' . $user -> getUid ()], 'write' => [ 'user:' . $user -> getUid ()]],
2019-05-09 18:54:39 +12:00
'type' => Auth :: TOKEN_TYPE_CONFIRM ,
'secret' => Auth :: hash ( $secret ), // One way hash encryption to protect DB leak
'expire' => time () + Auth :: TOKEN_EXPIRATION_CONFIRM ,
'userAgent' => $request -> getServer ( 'HTTP_USER_AGENT' , 'UNKNOWN' ),
'ip' => $request -> getIP (),
]), Document :: SET_TYPE_APPEND );
$user = $projectDB -> updateDocument ( $user -> getArrayCopy ());
2019-09-07 05:10:41 +12:00
if ( false === $user ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed saving user to DB' , 500 );
}
2019-09-29 11:55:54 +13:00
$confirm = Template :: parseURL ( $confirm );
$confirm [ 'query' ] = Template :: mergeQuery ((( isset ( $confirm [ 'query' ])) ? $confirm [ 'query' ] : '' ), [ 'userId' => $user -> getUid (), 'token' => $secret ]);
$confirm = Template :: unParseURL ( $confirm );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
$body = new Template ( __DIR__ . '/../config/locale/templates/' . Locale :: getText ( 'auth.emails.confirm.body' ));
2019-05-09 18:54:39 +12:00
$body
-> setParam ( '{{direction}}' , Locale :: getText ( 'settings.direction' ))
-> setParam ( '{{project}}' , $project -> getAttribute ( 'name' , [ '[APP-NAME]' ]))
-> setParam ( '{{name}}' , $user -> getAttribute ( 'name' ))
2019-09-29 11:55:54 +13:00
-> setParam ( '{{redirect}}' , $confirm )
2019-05-09 18:54:39 +12:00
;
2019-08-31 18:02:21 +12:00
$mail = $register -> get ( 'smtp' ); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */
2019-05-09 18:54:39 +12:00
2019-08-09 09:49:46 +12:00
$mail -> addAddress ( $user -> getAttribute ( 'email' ), $user -> getAttribute ( 'name' ));
$mail -> Subject = Locale :: getText ( 'auth.emails.confirm.title' );
2019-09-07 05:10:41 +12:00
$mail -> Body = $body -> render ();
2019-08-09 09:49:46 +12:00
$mail -> AltBody = strip_tags ( $body -> render ());
2019-05-09 18:54:39 +12:00
2019-08-09 09:49:46 +12:00
try {
$mail -> send ();
2019-09-07 05:10:41 +12:00
} catch ( \Exception $error ) {
2019-08-25 17:16:17 +12:00
//throw new Exception('Problem sending mail: ' . $error->getError(), 500);
2019-05-09 18:54:39 +12:00
}
$response -> json ( array ( 'result' => 'success' ));
}
);
$utopia -> post ( '/v1/auth/login' )
-> desc ( 'Login User' )
-> label ( 'webhook' , 'auth.login' )
-> label ( 'scope' , 'auth' )
-> label ( 'sdk.namespace' , 'auth' )
-> label ( 'sdk.method' , 'login' )
2019-10-07 09:10:52 +13:00
-> label ( 'sdk.description' , '/docs/refernces/login.md' )
2019-05-09 18:54:39 +12:00
-> label ( 'sdk.cookies' , true )
-> label ( 'abuse-limit' , 10 )
-> label ( 'abuse-key' , 'url:{url},email:{param-email}' )
2019-10-07 09:10:52 +13:00
-> param ( 'email' , '' , function () { return new Email (); }, 'User account email address' )
-> param ( 'password' , '' , function () { return new Password (); }, 'User account password' )
-> param ( 'success' , null , function () use ( $clients ) { return new Host ( $clients ); }, 'URL to redirect back to your app after a successful login attempt.' )
-> param ( 'failure' , null , function () use ( $clients ) { return new Host ( $clients ); }, 'URL to redirect back to your app after a failed login attempt.' )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-07 05:10:41 +12:00
function ( $email , $password , $success , $failure ) use ( $response , $request , $projectDB , $audit , $webhook ) {
2019-05-09 18:54:39 +12:00
$profile = $projectDB -> getCollection ([ // Get user by email address
'limit' => 1 ,
'first' => true ,
'filters' => [
2019-09-07 05:10:41 +12:00
'$collection=' . Database :: SYSTEM_COLLECTION_USERS ,
'email=' . $email ,
],
2019-05-09 18:54:39 +12:00
]);
2019-09-07 05:10:41 +12:00
if ( ! $profile || ! Auth :: passwordVerify ( $password , $profile -> getAttribute ( 'password' ))) {
2019-05-09 18:54:39 +12:00
$audit
//->setParam('userId', $profile->getUid())
-> setParam ( 'event' , 'auth.failure' )
;
2019-09-07 05:10:41 +12:00
if ( $failure ) {
2019-09-16 04:50:57 +12:00
$response -> redirect ( $failure );
2019-09-07 05:10:41 +12:00
2019-09-16 04:50:57 +12:00
return ;
2019-05-09 18:54:39 +12:00
}
throw new Exception ( 'Invalid credentials' , 401 ); // Wrong password or username
}
$expiry = time () + Auth :: TOKEN_EXPIRATION_LOGIN_LONG ;
$secret = Auth :: tokenGenerator ();
$profile -> setAttribute ( 'tokens' , new Document ([
'$collection' => Database :: SYSTEM_COLLECTION_TOKENS ,
2019-09-07 05:10:41 +12:00
'$permissions' => [ 'read' => [ 'user:' . $profile -> getUid ()], 'write' => [ 'user:' . $profile -> getUid ()]],
2019-05-09 18:54:39 +12:00
'type' => Auth :: TOKEN_TYPE_LOGIN ,
'secret' => Auth :: hash ( $secret ), // On way hash encryption to protect DB leak
'expire' => $expiry ,
'userAgent' => $request -> getServer ( 'HTTP_USER_AGENT' , 'UNKNOWN' ),
'ip' => $request -> getIP (),
2019-09-07 05:10:41 +12:00
]), Document :: SET_TYPE_APPEND );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
Authorization :: setRole ( 'user:' . $profile -> getUid ());
2019-05-09 18:54:39 +12:00
$profile = $projectDB -> updateDocument ( $profile -> getArrayCopy ());
2019-09-07 05:10:41 +12:00
if ( false === $profile ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed saving user to DB' , 500 );
}
$webhook
-> setParam ( 'payload' , [
'name' => $profile -> getAttribute ( 'name' , '' ),
'email' => $profile -> getAttribute ( 'email' , '' ),
])
;
$audit
-> setParam ( 'userId' , $profile -> getUid ())
-> setParam ( 'event' , 'auth.login' )
;
$response
2019-08-24 19:30:12 +12:00
-> addCookie ( Auth :: $cookieName , Auth :: encodeSession ( $profile -> getUid (), $secret ), $expiry , '/' , COOKIE_DOMAIN , ( 'https' == $request -> getServer ( 'REQUEST_SCHEME' , 'https' )), true );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
if ( $success ) {
2019-05-09 18:54:39 +12:00
$response -> redirect ( $success );
}
$response
-> json ( array ( 'result' => 'success' ));
}
);
$utopia -> delete ( '/v1/auth/logout' )
-> desc ( 'Logout Current Session' )
-> label ( 'webhook' , 'auth.logout' )
-> label ( 'scope' , 'account' )
-> label ( 'sdk.namespace' , 'auth' )
-> label ( 'sdk.method' , 'logout' )
2019-10-07 09:10:52 +13:00
-> label ( 'sdk.description' , '/docs/refernces/logout.md' )
2019-05-09 18:54:39 +12:00
-> label ( 'abuse-limit' , 100 )
-> action (
2019-09-07 05:10:41 +12:00
function () use ( $response , $request , $user , $projectDB , $audit , $webhook ) {
2019-05-09 18:54:39 +12:00
$token = Auth :: tokenVerify ( $user -> getAttribute ( 'tokens' ), Auth :: TOKEN_TYPE_LOGIN , Auth :: $secret );
2019-09-07 05:10:41 +12:00
if ( ! $projectDB -> deleteDocument ( $token )) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed to remove token from DB' , 500 );
}
$webhook
-> setParam ( 'payload' , [
'name' => $user -> getAttribute ( 'name' , '' ),
'email' => $user -> getAttribute ( 'email' , '' ),
])
;
$audit -> setParam ( 'event' , 'auth.logout' );
$response
2019-08-24 19:30:12 +12:00
-> addCookie ( Auth :: $cookieName , '' , time () - 3600 , '/' , COOKIE_DOMAIN , ( 'https' == $request -> getServer ( 'REQUEST_SCHEME' , 'https' )), true )
2019-05-09 18:54:39 +12:00
-> json ( array ( 'result' => 'success' ))
;
}
);
2019-07-21 23:43:06 +12:00
$utopia -> delete ( '/v1/auth/logout/:id' )
2019-05-09 18:54:39 +12:00
-> desc ( 'Logout Specific Session' )
-> label ( 'scope' , 'account' )
-> label ( 'sdk.namespace' , 'auth' )
-> label ( 'sdk.method' , 'logoutBySession' )
2019-10-07 09:10:52 +13:00
-> label ( 'sdk.description' , '/docs/refernces/logout-by-session.md' )
2019-05-09 18:54:39 +12:00
-> label ( 'abuse-limit' , 100 )
2019-10-07 09:10:52 +13:00
-> param ( 'id' , null , function () { return new UID (); }, 'User specific session unique ID number. if 0 delete all sessions.' )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-07 05:10:41 +12:00
function ( $id ) use ( $response , $request , $user , $projectDB , $audit ) {
2019-05-09 18:54:39 +12:00
$tokens = $user -> getAttribute ( 'tokens' , []);
2019-09-07 05:10:41 +12:00
foreach ( $tokens as $token ) { /* @var $token Document */
if (( $id == $token -> getUid () || ( $id == 0 )) && Auth :: TOKEN_TYPE_LOGIN == $token -> getAttribute ( 'type' )) {
if ( ! $projectDB -> deleteDocument ( $token -> getUid ())) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed to remove token from DB' , 500 );
}
$audit
-> setParam ( 'event' , 'auth.logout' )
2019-09-07 05:10:41 +12:00
-> setParam ( 'resource' , '/auth/token/' . $token -> getUid ())
2019-05-09 18:54:39 +12:00
;
2019-09-07 05:10:41 +12:00
if ( $token -> getAttribute ( 'secret' ) == Auth :: hash ( Auth :: $secret )) { // If current session delete cookies
2019-08-24 19:30:12 +12:00
$response -> addCookie ( Auth :: $cookieName , '' , time () - 3600 , '/' , COOKIE_DOMAIN , ( 'https' == $request -> getServer ( 'REQUEST_SCHEME' , 'https' )), true );
2019-05-09 18:54:39 +12:00
}
}
}
$response -> json ( array ( 'result' => 'success' ));
}
);
$utopia -> post ( '/v1/auth/recovery' )
-> desc ( 'Password Recovery' )
-> label ( 'scope' , 'auth' )
-> label ( 'sdk.namespace' , 'auth' )
-> label ( 'sdk.method' , 'recovery' )
2019-10-07 09:10:52 +13:00
-> label ( 'sdk.description' , '/docs/refernces/recovery.md' )
2019-05-09 18:54:39 +12:00
-> label ( 'abuse-limit' , 10 )
-> label ( 'abuse-key' , 'url:{url},email:{param-email}' )
2019-10-07 09:10:52 +13:00
-> param ( 'email' , '' , function () { return new Email (); }, 'User account email address.' )
-> param ( 'reset' , '' , function () use ( $clients ) { return new Host ( $clients ); }, 'Reset URL in your app to redirect the user after the reset token has been sent to the user email.' )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-27 10:02:03 +12:00
function ( $email , $reset ) use ( $request , $response , $projectDB , $register , $audit , $project ) {
2019-05-09 18:54:39 +12:00
$profile = $projectDB -> getCollection ([ // Get user by email address
'limit' => 1 ,
'first' => true ,
'filters' => [
2019-09-07 05:10:41 +12:00
'$collection=' . Database :: SYSTEM_COLLECTION_USERS ,
'email=' . $email ,
],
2019-05-09 18:54:39 +12:00
]);
2019-09-07 05:10:41 +12:00
if ( empty ( $profile )) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'User not found' , 404 ); // TODO maybe hide this
}
$secret = Auth :: tokenGenerator ();
$profile -> setAttribute ( 'tokens' , new Document ([
'$collection' => Database :: SYSTEM_COLLECTION_TOKENS ,
2019-09-07 05:10:41 +12:00
'$permissions' => [ 'read' => [ 'user:' . $profile -> getUid ()], 'write' => [ 'user:' . $profile -> getUid ()]],
2019-05-09 18:54:39 +12:00
'type' => Auth :: TOKEN_TYPE_RECOVERY ,
'secret' => Auth :: hash ( $secret ), // On way hash encryption to protect DB leak
'expire' => time () + Auth :: TOKEN_EXPIRATION_RECOVERY ,
'userAgent' => $request -> getServer ( 'HTTP_USER_AGENT' , 'UNKNOWN' ),
'ip' => $request -> getIP (),
]), Document :: SET_TYPE_APPEND );
2019-09-07 05:10:41 +12:00
Authorization :: setRole ( 'user:' . $profile -> getUid ());
2019-05-09 18:54:39 +12:00
$profile = $projectDB -> updateDocument ( $profile -> getArrayCopy ());
2019-09-07 05:10:41 +12:00
if ( false === $profile ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed to save user to DB' , 500 );
}
2019-09-27 10:02:03 +12:00
$reset = Template :: parseURL ( $reset );
$reset [ 'query' ] = Template :: mergeQuery ((( isset ( $reset [ 'query' ])) ? $reset [ 'query' ] : '' ), [ 'userId' => $profile -> getUid (), 'token' => $secret ]);
$reset = Template :: unParseURL ( $reset );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
$body = new Template ( __DIR__ . '/../config/locale/templates/' . Locale :: getText ( 'auth.emails.recovery.body' ));
2019-05-09 18:54:39 +12:00
$body
-> setParam ( '{{direction}}' , Locale :: getText ( 'settings.direction' ))
-> setParam ( '{{project}}' , $project -> getAttribute ( 'name' , [ '[APP-NAME]' ]))
-> setParam ( '{{name}}' , $profile -> getAttribute ( 'name' ))
2019-09-27 10:02:03 +12:00
-> setParam ( '{{redirect}}' , $reset )
2019-05-09 18:54:39 +12:00
;
2019-08-31 18:02:21 +12:00
$mail = $register -> get ( 'smtp' ); /* @var $mail \PHPMailer\PHPMailer\PHPMailer */
2019-05-09 18:54:39 +12:00
2019-08-09 09:49:46 +12:00
$mail -> addAddress ( $profile -> getAttribute ( 'email' , '' ), $profile -> getAttribute ( 'name' , '' ));
2019-05-09 18:54:39 +12:00
2019-08-09 09:49:46 +12:00
$mail -> Subject = Locale :: getText ( 'auth.emails.recovery.title' );
2019-09-07 05:10:41 +12:00
$mail -> Body = $body -> render ();
2019-08-09 09:49:46 +12:00
$mail -> AltBody = strip_tags ( $body -> render ());
try {
$mail -> send ();
2019-09-07 05:10:41 +12:00
} catch ( \Exception $error ) {
2019-08-25 17:16:17 +12:00
//throw new Exception('Problem sending mail: ' . $error->getError(), 500);
2019-05-09 18:54:39 +12:00
}
$audit
-> setParam ( 'userId' , $profile -> getUid ())
-> setParam ( 'event' , 'auth.recovery' )
;
$response -> json ( array ( 'result' => 'success' ));
}
);
$utopia -> put ( '/v1/auth/recovery/reset' )
-> desc ( 'Password Reset' )
-> label ( 'scope' , 'auth' )
-> label ( 'sdk.namespace' , 'auth' )
-> label ( 'sdk.method' , 'recoveryReset' )
2019-10-07 09:10:52 +13:00
-> label ( 'sdk.description' , '/docs/refernces/recovery-reset.md' )
2019-05-09 18:54:39 +12:00
-> label ( 'abuse-limit' , 10 )
-> label ( 'abuse-key' , 'url:{url},userId:{param-userId}' )
2019-10-07 09:10:52 +13:00
-> param ( 'userId' , '' , function () { return new UID (); }, 'User account email address.' )
-> param ( 'token' , '' , function () { return new Text ( 256 ); }, 'Valid reset token.' )
-> param ( 'password-a' , '' , function () { return new Password (); }, 'New password.' )
-> param ( 'password-b' , '' , function () { return new Password (); }, 'New password again.' )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-07 05:10:41 +12:00
function ( $userId , $token , $passwordA , $passwordB ) use ( $response , $projectDB , $audit ) {
if ( $passwordA !== $passwordB ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Passwords must match' , 400 );
}
$profile = $projectDB -> getCollection ([ // Get user by email address
'limit' => 1 ,
'first' => true ,
'filters' => [
2019-09-07 05:10:41 +12:00
'$collection=' . Database :: SYSTEM_COLLECTION_USERS ,
'$uid=' . $userId ,
],
2019-05-09 18:54:39 +12:00
]);
2019-09-07 05:10:41 +12:00
if ( empty ( $profile )) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'User not found' , 404 ); // TODO maybe hide this
}
$token = Auth :: tokenVerify ( $profile -> getAttribute ( 'tokens' , []), Auth :: TOKEN_TYPE_RECOVERY , $token );
2019-09-07 05:10:41 +12:00
if ( ! $token ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Recovery token is not valid' , 401 );
}
2019-09-07 05:10:41 +12:00
Authorization :: setRole ( 'user:' . $profile -> getUid ());
2019-05-09 18:54:39 +12:00
$profile = $projectDB -> updateDocument ( array_merge ( $profile -> getArrayCopy (), [
'password' => Auth :: passwordHash ( $passwordA ),
'password-update' => time (),
'confirm' => true ,
]));
2019-09-07 05:10:41 +12:00
if ( false === $profile ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed saving user to DB' , 500 );
}
2019-09-07 05:10:41 +12:00
if ( ! $projectDB -> deleteDocument ( $token )) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed to remove token from DB' , 500 );
}
$audit
-> setParam ( 'userId' , $profile -> getUid ())
-> setParam ( 'event' , 'auth.recovery.reset' )
;
$response -> json ( array ( 'result' => 'success' ));
}
);
2019-09-03 19:24:35 +12:00
$utopia -> get ( '/v1/auth/oauth/:provider' )
2019-05-09 18:54:39 +12:00
-> desc ( 'OAuth Login' )
2019-09-07 05:10:41 +12:00
-> label ( 'error' , __DIR__ . '/../views/general/error.phtml' )
2019-05-09 18:54:39 +12:00
-> label ( 'scope' , 'auth' )
-> label ( 'sdk.namespace' , 'auth' )
-> label ( 'sdk.method' , 'oauth' )
2019-09-29 22:42:06 +13:00
-> label ( 'sdk.location' , true )
2019-05-09 18:54:39 +12:00
-> label ( 'abuse-limit' , 50 )
-> label ( 'abuse-key' , 'ip:{ip}' )
2019-10-07 09:10:52 +13:00
-> param ( 'provider' , '' , function () use ( $providers ) { return new WhiteList ( array_keys ( $providers )); }, 'OAuth Provider' )
-> param ( 'success' , '' , function () use ( $clients ) { return new Host ( $clients ); }, 'URL to redirect back to your app after a successful login attempt.' , true )
-> param ( 'failure' , '' , function () use ( $clients ) { return new Host ( $clients ); }, 'URL to redirect back to your app after a failed login attempt.' , true )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-07 05:10:41 +12:00
function ( $provider , $success , $failure ) use ( $response , $request , $project ) {
$callback = $request -> getServer ( 'REQUEST_SCHEME' , 'https' ) . '://' . $request -> getServer ( 'HTTP_HOST' ) . '/v1/auth/oauth/callback/' . $provider . '/' . $project -> getUid ();
$appId = $project -> getAttribute ( 'usersOauth' . ucfirst ( $provider ) . 'Appid' , '' );
$appSecret = $project -> getAttribute ( 'usersOauth' . ucfirst ( $provider ) . 'Secret' , '{}' );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
$appSecret = json_decode ( $appSecret , true );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
if ( ! empty ( $appSecret ) && isset ( $appSecret [ 'version' ])) {
$key = $request -> getServer ( '_APP_OPENSSL_KEY_V' . $appSecret [ 'version' ]);
$appSecret = OpenSSL :: decrypt ( $appSecret [ 'data' ], $appSecret [ 'method' ], $key , 0 , hex2bin ( $appSecret [ 'iv' ]), hex2bin ( $appSecret [ 'tag' ]));
2019-05-09 18:54:39 +12:00
}
2019-09-07 05:10:41 +12:00
if ( empty ( $appId ) || empty ( $appSecret )) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Provider is undefined, configure provider app ID and app secret key to continue' , 412 );
}
2019-09-07 05:10:41 +12:00
$classname = 'Auth\\OAuth\\' . ucfirst ( $provider );
if ( ! class_exists ( $classname )) {
2019-09-04 08:37:34 +12:00
throw new Exception ( 'Provider is not supported' , 501 );
2019-05-09 18:54:39 +12:00
}
2019-09-04 08:37:34 +12:00
$oauth = new $classname ( $appId , $appSecret , $callback , [ 'success' => $success , 'failure' => $failure ]);
2019-05-09 18:54:39 +12:00
$response -> redirect ( $oauth -> getLoginURL ());
}
);
2019-09-03 19:24:35 +12:00
$utopia -> get ( '/v1/auth/oauth/callback/:provider/:projectId' )
2019-05-09 18:54:39 +12:00
-> desc ( 'OAuth Callback' )
2019-09-07 05:10:41 +12:00
-> label ( 'error' , __DIR__ . '/../views/general/error.phtml' )
2019-05-09 18:54:39 +12:00
-> label ( 'scope' , 'auth' )
-> label ( 'abuse-limit' , 50 )
-> label ( 'abuse-key' , 'ip:{ip}' )
2019-09-30 10:09:24 +13:00
-> label ( 'docs' , false )
2019-10-07 09:10:52 +13:00
-> param ( 'projectId' , '' , function () { return new Text ( 1024 ); }, 'Project unique ID' )
-> param ( 'provider' , '' , function () use ( $providers ) { return new WhiteList ( array_keys ( $providers )); }, 'OAuth provider' )
-> param ( 'code' , '' , function () { return new Text ( 1024 ); }, 'OAuth code' )
-> param ( 'state' , '' , function () { return new Text ( 2048 ); }, 'Login state params' , true )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-07 05:10:41 +12:00
function ( $projectId , $provider , $code , $state ) use ( $response , $request , $domain ) {
$response -> redirect ( $request -> getServer ( 'REQUEST_SCHEME' , 'https' ) . '://' . $domain . '/v1/auth/oauth/' . $provider . '/redirect?'
. http_build_query ([ 'project' => $projectId , 'code' => $code , 'state' => $state ]));
2019-05-09 18:54:39 +12:00
}
);
2019-09-03 19:24:35 +12:00
$utopia -> get ( '/v1/auth/oauth/:provider/redirect' )
2019-05-09 18:54:39 +12:00
-> desc ( 'OAuth Redirect' )
2019-09-07 05:10:41 +12:00
-> label ( 'error' , __DIR__ . '/../views/general/error.phtml' )
2019-05-09 18:54:39 +12:00
-> label ( 'webhook' , 'auth.oauth' )
-> label ( 'scope' , 'auth' )
-> label ( 'abuse-limit' , 50 )
-> label ( 'abuse-key' , 'ip:{ip}' )
-> label ( 'docs' , false )
2019-10-07 09:10:52 +13:00
-> param ( 'provider' , '' , function () use ( $providers ) { return new WhiteList ( array_keys ( $providers )); }, 'OAuth provider' )
-> param ( 'code' , '' , function () { return new Text ( 1024 ); }, 'OAuth code' )
-> param ( 'state' , '' , function () { return new Text ( 2048 ); }, 'OAuth state params' , true )
2019-05-09 18:54:39 +12:00
-> action (
2019-09-07 05:10:41 +12:00
function ( $provider , $code , $state ) use ( $response , $request , $user , $projectDB , $project , $audit ) {
$callback = $request -> getServer ( 'REQUEST_SCHEME' , 'https' ) . '://' . $request -> getServer ( 'HTTP_HOST' ) . '/v1/auth/oauth/callback/' . $provider . '/' . $project -> getUid ();
$defaultState = [ 'success' => $project -> getAttribute ( 'url' , '' ), 'failure' => '' ];
$validateURL = new URL ();
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
$appId = $project -> getAttribute ( 'usersOauth' . ucfirst ( $provider ) . 'Appid' , '' );
$appSecret = $project -> getAttribute ( 'usersOauth' . ucfirst ( $provider ) . 'Secret' , '{}' );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
$appSecret = json_decode ( $appSecret , true );
2019-05-09 18:54:39 +12:00
2019-09-07 05:10:41 +12:00
if ( ! empty ( $appSecret ) && isset ( $appSecret [ 'version' ])) {
$key = $request -> getServer ( '_APP_OPENSSL_KEY_V' . $appSecret [ 'version' ]);
$appSecret = OpenSSL :: decrypt ( $appSecret [ 'data' ], $appSecret [ 'method' ], $key , 0 , hex2bin ( $appSecret [ 'iv' ]), hex2bin ( $appSecret [ 'tag' ]));
2019-05-09 18:54:39 +12:00
}
2019-09-07 05:10:41 +12:00
$classname = 'Auth\\OAuth\\' . ucfirst ( $provider );
2019-09-04 08:37:34 +12:00
2019-09-07 05:10:41 +12:00
if ( ! class_exists ( $classname )) {
2019-09-04 08:37:34 +12:00
throw new Exception ( 'Provider is not supported' , 501 );
2019-05-09 18:54:39 +12:00
}
2019-09-04 08:37:34 +12:00
$oauth = new $classname ( $appId , $appSecret , $callback );
2019-10-07 01:56:44 +13:00
if ( ! empty ( $state )) {
try {
$state = array_merge ( $defaultState , $oauth -> parseState ( $state ));
} catch ( \Exception $exception ) {
throw new Exception ( 'Failed to parse login state params as passed from OAuth provider' );
}
} else {
$state = $defaultState ;
}
if ( ! $validateURL -> isValid ( $state [ 'success' ])) {
throw new Exception ( 'Invalid redirect URL for success login' , 400 );
}
if ( ! empty ( $state [ 'failure' ]) && ! $validateURL -> isValid ( $state [ 'failure' ])) {
throw new Exception ( 'Invalid redirect URL for failure login' , 400 );
}
2019-05-09 18:54:39 +12:00
$accessToken = $oauth -> getAccessToken ( $code );
2019-09-07 05:10:41 +12:00
if ( empty ( $accessToken )) {
if ( ! empty ( $state [ 'failure' ])) {
2019-05-09 18:54:39 +12:00
$response -> redirect ( $state [ 'failure' ], 301 , 0 );
}
throw new Exception ( 'Failed to obtain access token' );
}
$oauthID = $oauth -> getUserID ( $accessToken );
2019-10-02 05:11:51 +13:00
2019-09-07 05:10:41 +12:00
if ( empty ( $oauthID )) {
if ( ! empty ( $state [ 'failure' ])) {
2019-05-09 18:54:39 +12:00
$response -> redirect ( $state [ 'failure' ], 301 , 0 );
}
throw new Exception ( 'Missing ID from OAuth provider' , 400 );
}
$current = Auth :: tokenVerify ( $user -> getAttribute ( 'tokens' , []), Auth :: TOKEN_TYPE_LOGIN , Auth :: $secret );
2019-10-02 05:11:51 +13:00
2019-09-07 05:10:41 +12:00
if ( $current ) {
2019-05-09 18:54:39 +12:00
$projectDB -> deleteDocument ( $current ); //throw new Exception('User already logged in', 401);
}
$user = ( empty ( $user -> getUid ())) ? $projectDB -> getCollection ([ // Get user by provider id
'limit' => 1 ,
'first' => true ,
'filters' => [
2019-09-07 05:10:41 +12:00
'$collection=' . Database :: SYSTEM_COLLECTION_USERS ,
'oauth' . ucfirst ( $provider ) . '=' . $oauthID ,
],
2019-05-09 18:54:39 +12:00
]) : $user ;
2019-09-07 05:10:41 +12:00
if ( empty ( $user )) { // No user logged in or with oauth provider ID, create new one or connect with account with same email
$name = $oauth -> getUserName ( $accessToken );
$email = $oauth -> getUserEmail ( $accessToken );
2019-05-09 18:54:39 +12:00
$user = $projectDB -> getCollection ([ // Get user by provider email address
'limit' => 1 ,
'first' => true ,
'filters' => [
2019-09-07 05:10:41 +12:00
'$collection=' . Database :: SYSTEM_COLLECTION_USERS ,
'email=' . $email ,
],
2019-05-09 18:54:39 +12:00
]);
2019-09-29 11:34:35 +13:00
if ( ! $user || empty ( $user -> getUid ())) { // Last option -> create user alone, generate random password
2019-05-09 18:54:39 +12:00
Authorization :: disable ();
$user = $projectDB -> createDocument ([
'$collection' => Database :: SYSTEM_COLLECTION_USERS ,
'$permissions' => [ 'read' => [ '*' ], 'write' => [ 'user:{self}' ]],
'email' => $email ,
'status' => Auth :: USER_STATUS_ACTIVATED , // Email should already be authenticated by OAuth provider
'password' => Auth :: passwordHash ( Auth :: passwordGenerator ()),
'password-update' => time (),
'registration' => time (),
'confirm' => true ,
'reset' => false ,
'name' => $name ,
]);
Authorization :: enable ();
2019-09-07 05:10:41 +12:00
if ( false === $user ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed saving user to DB' , 500 );
}
}
}
// Create login token, confirm user account and update OAuth ID and Access Token
$secret = Auth :: tokenGenerator ();
$expiry = time () + Auth :: TOKEN_EXPIRATION_LOGIN_LONG ;
$user
2019-09-07 05:10:41 +12:00
-> setAttribute ( 'oauth' . ucfirst ( $provider ), $oauthID )
-> setAttribute ( 'oauth' . ucfirst ( $provider ) . 'AccessToken' , $accessToken )
2019-05-09 18:54:39 +12:00
-> setAttribute ( 'status' , Auth :: USER_STATUS_ACTIVATED )
-> setAttribute ( 'tokens' , new Document ([
'$collection' => Database :: SYSTEM_COLLECTION_TOKENS ,
2019-09-07 05:10:41 +12:00
'$permissions' => [ 'read' => [ 'user:' . $user [ '$uid' ]], 'write' => [ 'user:' . $user [ '$uid' ]]],
2019-05-09 18:54:39 +12:00
'type' => Auth :: TOKEN_TYPE_LOGIN ,
'secret' => Auth :: hash ( $secret ), // On way hash encryption to protect DB leak
'expire' => $expiry ,
'userAgent' => $request -> getServer ( 'HTTP_USER_AGENT' , 'UNKNOWN' ),
'ip' => $request -> getIP (),
]), Document :: SET_TYPE_APPEND )
;
2019-09-07 05:10:41 +12:00
Authorization :: setRole ( 'user:' . $user -> getUid ());
2019-05-09 18:54:39 +12:00
$user = $projectDB -> updateDocument ( $user -> getArrayCopy ());
2019-09-07 05:10:41 +12:00
if ( false === $user ) {
2019-05-09 18:54:39 +12:00
throw new Exception ( 'Failed saving user to DB' , 500 );
}
$audit
-> setParam ( 'userId' , $user -> getUid ())
-> setParam ( 'event' , 'auth.oauth.login' )
-> setParam ( 'data' , [ 'provider' => $provider ])
;
$response
2019-08-24 19:30:12 +12:00
-> addCookie ( Auth :: $cookieName , Auth :: encodeSession ( $user -> getUid (), $secret ), $expiry , '/' , COOKIE_DOMAIN , ( 'https' == $request -> getServer ( 'REQUEST_SCHEME' , 'https' )), true )
2019-05-09 18:54:39 +12:00
;
$response -> redirect ( $state [ 'success' ]);
}
2019-09-07 05:10:41 +12:00
);