mirror of
https://github.com/anikeen-com/id.git
synced 2026-11-06 15:48:36 +00:00
Look up users by email and document invitations
This commit is contained in:
2 files changed
+92
-7
No files matched your search
@@ -5,7 +5,9 @@ namespace Anikeen\Id\Concerns;
|
|||||||
|
|
||||||
use Anikeen\Id\ApiOperations\Get;
|
use Anikeen\Id\ApiOperations\Get;
|
||||||
use Anikeen\Id\ApiOperations\Post;
|
use Anikeen\Id\ApiOperations\Post;
|
||||||
|
use Anikeen\Id\Exceptions\ApiException;
|
||||||
use Anikeen\Id\Result;
|
use Anikeen\Id\Result;
|
||||||
|
use Anikeen\Id\Support\AppRequest;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
|
||||||
trait ManagesUsers
|
trait ManagesUsers
|
||||||
@@ -24,20 +26,32 @@ trait ManagesUsers
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new user on behalf of the current user.
|
* Finds the account of an email address or creates it (trusted apps, app token). The email
|
||||||
|
* address is matched regardless of case; a new account stores it in lower case, unverified.
|
||||||
|
* An existing account is returned unchanged (only a missing name is filled in).
|
||||||
*
|
*
|
||||||
* @param array{
|
* @param array{
|
||||||
* first_name: null|string,
|
* name?: null|string,
|
||||||
* last_name: null|string,
|
* first_name?: null|string,
|
||||||
* username: null|string,
|
* last_name?: null|string,
|
||||||
|
* username?: null|string,
|
||||||
* email: string,
|
* email: string,
|
||||||
* password: null|string
|
* password?: null|string,
|
||||||
|
* password_confirmation?: null|string,
|
||||||
|
* send_invitation?: bool
|
||||||
* } $attributes The user data
|
* } $attributes The user data
|
||||||
* - first_name: The first name (optional)
|
* - name: The display name (optional)
|
||||||
* - last_name: The last name (optional)
|
* - first_name: The first name (optional, joined with last_name into name)
|
||||||
|
* - last_name: The last name (optional, joined with first_name into name)
|
||||||
* - username: The username (optional)
|
* - username: The username (optional)
|
||||||
* - email: The email (required)
|
* - email: The email (required)
|
||||||
* - password: The password (optional, can be reset by the user if not provided)
|
* - password: The password (optional, can be reset by the user if not provided)
|
||||||
|
* - password_confirmation: Defaults to password
|
||||||
|
* - send_invitation: Send the user an invitation mail with a link to set a password
|
||||||
|
* (optional, only when this call created the account without a
|
||||||
|
* password, never for an existing account)
|
||||||
|
* @return Result `data` is the user plus `created` (this call created the account) and
|
||||||
|
* `invitation_sent` (the invitation mail was sent); 201 when created, 200 when it existed
|
||||||
* @throws Throwable
|
* @throws Throwable
|
||||||
*/
|
*/
|
||||||
public function createUser(array $attributes): Result
|
public function createUser(array $attributes): Result
|
||||||
@@ -57,6 +71,30 @@ trait ManagesUsers
|
|||||||
return $this->asApp()->post('v1/admin/users', $attributes);
|
return $this->asApp()->post('v1/admin/users', $attributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Looks up the account of an email address without creating or changing anything (trusted
|
||||||
|
* apps, app token). The email address is matched regardless of case.
|
||||||
|
*
|
||||||
|
* `data` is `object{id: string, name: ?string, email: string, email_verified: bool,
|
||||||
|
* has_billing_address: bool}`.
|
||||||
|
*
|
||||||
|
* @return Result|null null when no account uses the email address
|
||||||
|
* @throws ApiException when the request fails for another reason
|
||||||
|
*/
|
||||||
|
public function findUserByEmail(string $email): ?Result
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return AppRequest::send('POST', 'v1/admin/users/lookup', ['email' => $email]);
|
||||||
|
} catch (ApiException $e) {
|
||||||
|
// only Anikeen ID's own answer means "no account", not e.g. an unknown route
|
||||||
|
if ($e->isNotFound() && $e->error === 'user_not_found') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Find the user of a social login or create it (trusted apps, app token).
|
* Find the user of a social login or create it (trusted apps, app token).
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace Anikeen\Id\Tests;
|
|||||||
|
|
||||||
use Anikeen\Id\AnikeenId;
|
use Anikeen\Id\AnikeenId;
|
||||||
use Anikeen\Id\Billable;
|
use Anikeen\Id\Billable;
|
||||||
|
use Anikeen\Id\Exceptions\ApiException;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,6 +91,52 @@ class EndpointsTest extends TestCase
|
|||||||
$this->assertSame(['email' => 'erika@example.com', 'password' => 'secret-123', 'name' => 'Erika Mustermann', 'password_confirmation' => 'secret-123'], $this->sentJson(0));
|
$this->assertSame(['email' => 'erika@example.com', 'password' => 'secret-123', 'name' => 'Erika Mustermann', 'password_confirmation' => 'secret-123'], $this->sentJson(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_create_user_can_send_an_invitation(): void
|
||||||
|
{
|
||||||
|
$this->respond(201, ['data' => ['id' => '01JNEW', 'email' => 'erika@example.com', 'created' => true, 'invitation_sent' => true]]);
|
||||||
|
|
||||||
|
$result = app(AnikeenId::class)->createUser(['name' => 'Erika Mustermann', 'email' => 'erika@example.com', 'send_invitation' => true]);
|
||||||
|
|
||||||
|
$this->assertSame('POST /api/v1/admin/users Bearer app-token', $this->sentCall(0));
|
||||||
|
$this->assertSame(['name' => 'Erika Mustermann', 'email' => 'erika@example.com', 'send_invitation' => true], $this->sentJson(0));
|
||||||
|
$this->assertTrue($result->data->created);
|
||||||
|
$this->assertTrue($result->data->invitation_sent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_find_user_by_email_uses_the_lookup_endpoint(): void
|
||||||
|
{
|
||||||
|
$this->respond(200, ['data' => ['id' => '01JUSER', 'name' => 'Erika', 'email' => 'erika@example.com', 'email_verified' => true, 'has_billing_address' => false]]);
|
||||||
|
|
||||||
|
$result = app(AnikeenId::class)->findUserByEmail('Erika@Example.com');
|
||||||
|
|
||||||
|
$this->assertSame('POST /api/v1/admin/users/lookup Bearer app-token', $this->sentCall(0));
|
||||||
|
$this->assertSame(['email' => 'Erika@Example.com'], $this->sentJson(0));
|
||||||
|
$this->assertSame('01JUSER', $result->data->id);
|
||||||
|
$this->assertFalse($result->data->has_billing_address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_find_user_by_email_returns_null_without_an_account(): void
|
||||||
|
{
|
||||||
|
$this->respond(404, ['message' => 'User not found.', 'error' => 'user_not_found']);
|
||||||
|
|
||||||
|
$this->assertNull(app(AnikeenId::class)->findUserByEmail('nobody@example.com'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_find_user_by_email_throws_other_failures(): void
|
||||||
|
{
|
||||||
|
$this->respond(404, ['message' => 'Not found.']); // e.g. an Anikeen ID without the lookup
|
||||||
|
$this->respond(500, ['message' => 'Server Error']);
|
||||||
|
|
||||||
|
foreach ([404, 500] as $status) {
|
||||||
|
try {
|
||||||
|
app(AnikeenId::class)->findUserByEmail('erika@example.com');
|
||||||
|
$this->fail('Expected an ApiException.');
|
||||||
|
} catch (ApiException $e) {
|
||||||
|
$this->assertSame($status, $e->status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function test_email_check_and_socialite_users(): void
|
public function test_email_check_and_socialite_users(): void
|
||||||
{
|
{
|
||||||
$this->respond(200, ['data' => ['exists' => true]]);
|
$this->respond(200, ['data' => ['exists' => true]]);
|
||||||
|
|||||||
Reference in new issue
Block a user