stack/apps/e2e/tests/backend/endpoints/api/v1/users.test.ts
2024-08-10 09:45:48 -07:00

1173 lines
38 KiB
TypeScript

import { generateSecureRandomString } from "@stackframe/stack-shared/dist/utils/crypto";
import { describe } from "vitest";
import { it } from "../../../../helpers";
import { Auth, InternalProjectKeys, backendContext, niceBackendFetch } from "../../../backend-helpers";
describe("without project access", () => {
backendContext.set({
projectKeys: "no-project",
});
it("should not be able to read own user", async ({ expect }) => {
await backendContext.with({
projectKeys: InternalProjectKeys,
}, async () => {
await Auth.Otp.signIn();
});
const response = await niceBackendFetch("/api/v1/users/me");
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 400,
"body": {
"code": "ACCESS_TYPE_REQUIRED",
"error": "You must specify an access level for this Stack project. Make sure project API keys are provided (eg. x-stack-publishable-client-key) and you set the x-stack-access-type header to 'client', 'server', or 'admin'.\\n\\nFor more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/auth#authentication",
},
"headers": Headers {
"x-stack-known-error": "ACCESS_TYPE_REQUIRED",
<some fields may have been hidden>,
},
}
`);
});
it("should not be able to list users", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users");
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 400,
"body": {
"code": "ACCESS_TYPE_REQUIRED",
"error": "You must specify an access level for this Stack project. Make sure project API keys are provided (eg. x-stack-publishable-client-key) and you set the x-stack-access-type header to 'client', 'server', or 'admin'.\\n\\nFor more information, see the docs on REST API authentication: https://docs.stack-auth.com/rest-api/auth#authentication",
},
"headers": Headers {
"x-stack-known-error": "ACCESS_TYPE_REQUIRED",
<some fields may have been hidden>,
},
}
`);
});
});
describe("with client access", () => {
it("should not be able to read own user if not signed in", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 400,
"body": {
"code": "CANNOT_GET_OWN_USER_WITHOUT_USER",
"error": "You have specified 'me' as a userId, but did not provide authentication for a user.",
},
"headers": Headers {
"x-stack-known-error": "CANNOT_GET_OWN_USER_WITHOUT_USER",
<some fields may have been hidden>,
},
}
`);
});
it("should be able to read own user if signed in", async ({ expect }) => {
await Auth.Otp.signIn();
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should be able to read own user if signed in even without refresh token", async ({ expect }) => {
await Auth.Otp.signIn();
backendContext.set({ userAuth: { ...backendContext.value.userAuth, refreshToken: undefined } });
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should not be able to read own user without access token even if refresh token is given", async ({ expect }) => {
await Auth.Otp.signIn();
backendContext.set({ userAuth: { ...backendContext.value.userAuth, accessToken: undefined } });
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 400,
"body": {
"code": "CANNOT_GET_OWN_USER_WITHOUT_USER",
"error": "You have specified 'me' as a userId, but did not provide authentication for a user.",
},
"headers": Headers {
"x-stack-known-error": "CANNOT_GET_OWN_USER_WITHOUT_USER",
<some fields may have been hidden>,
},
}
`);
});
it("should return access token invalid error when reading own user with invalid access token", async ({ expect }) => {
await Auth.Otp.signIn();
backendContext.set({ userAuth: { ...backendContext.value.userAuth, accessToken: "12341234" } });
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 401,
"body": {
"code": "UNPARSABLE_ACCESS_TOKEN",
"error": "Access token is not parsable.",
},
"headers": Headers {
"x-stack-known-error": "UNPARSABLE_ACCESS_TOKEN",
<some fields may have been hidden>,
},
}
`);
});
it("should be able to update own user", async ({ expect }) => {
await Auth.Otp.signIn();
const response1 = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
method: "PATCH",
body: {
display_name: "John Doe",
},
});
expect(response1).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": "John Doe",
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
const response2 = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
method: "PATCH",
body: {
client_metadata: { key: "value" },
},
});
expect(response2).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": { "key": "value" },
"connected_accounts": [],
"display_name": "John Doe",
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it.todo("should be able to set own profile image URL with an image HTTP URL, and the new profile image URL should be a different HTTP URL on our storage service");
it.todo("should be able to set own profile image URL with a base64 data URL, and the new profile image URL should be a different HTTP URL on our storage service");
it.todo("should not be able to set own profile image URL with a file: protocol URL");
it.todo("should not be able to set own profile image URL to a localhost/non-public URL");
it("should not be able to set own server_metadata", async ({ expect }) => {
await Auth.Otp.signIn();
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
method: "PATCH",
body: {
display_name: "Johnny Doe",
server_metadata: { "key": "value" },
},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 400,
"body": {
"code": "SCHEMA_ERROR",
"details": { "message": "Request validation failed on PATCH /api/v1/users/me:\\n - body contains unknown properties: server_metadata" },
"error": "Request validation failed on PATCH /api/v1/users/me:\\n - body contains unknown properties: server_metadata",
},
"headers": Headers {
"x-stack-known-error": "SCHEMA_ERROR",
<some fields may have been hidden>,
},
}
`);
});
it("should not be able to delete own user", async ({ expect }) => {
await Auth.Otp.signIn();
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
method: "DELETE",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 401,
"body": {
"code": "INSUFFICIENT_ACCESS_TYPE",
"details": {
"actual_access_type": "client",
"allowed_access_types": [
"server",
"admin",
],
},
"error": "The x-stack-access-type header must be 'server' or 'admin', but was 'client'.",
},
"headers": Headers {
"x-stack-known-error": "INSUFFICIENT_ACCESS_TYPE",
<some fields may have been hidden>,
},
}
`);
});
it("should not be able to create a user", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users", {
accessType: "client",
method: "POST",
body: {},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 401,
"body": {
"code": "INSUFFICIENT_ACCESS_TYPE",
"details": {
"actual_access_type": "client",
"allowed_access_types": [
"server",
"admin",
],
},
"error": "The x-stack-access-type header must be 'server' or 'admin', but was 'client'.",
},
"headers": Headers {
"x-stack-known-error": "INSUFFICIENT_ACCESS_TYPE",
<some fields may have been hidden>,
},
}
`);
});
it("updating own display name to the empty string should set it to null", async ({ expect }) => {
await Auth.Otp.signIn();
const response1 = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
method: "PATCH",
body: {
display_name: "John Doe",
},
});
expect(response1).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": "John Doe",
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
const response2 = await niceBackendFetch("/api/v1/users/me", {
accessType: "client",
method: "PATCH",
body: {
display_name: "",
},
});
expect(response2).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should not be able to list users", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users", {
accessType: "client",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 401,
"body": {
"code": "INSUFFICIENT_ACCESS_TYPE",
"details": {
"actual_access_type": "client",
"allowed_access_types": [
"server",
"admin",
],
},
"error": "The x-stack-access-type header must be 'server' or 'admin', but was 'client'.",
},
"headers": Headers {
"x-stack-known-error": "INSUFFICIENT_ACCESS_TYPE",
<some fields may have been hidden>,
},
}
`);
});
it("should not be able to read a user", async ({ expect }) => {
await Auth.Otp.signIn();
backendContext.set({
userAuth: null,
});
const response = await niceBackendFetch("/api/v1/users/123", {
accessType: "client",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 401,
"body": {
"code": "INSUFFICIENT_ACCESS_TYPE",
"details": {
"actual_access_type": "client",
"allowed_access_types": [
"server",
"admin",
],
},
"error": "The x-stack-access-type header must be 'server' or 'admin', but was 'client'.",
},
"headers": Headers {
"x-stack-known-error": "INSUFFICIENT_ACCESS_TYPE",
<some fields may have been hidden>,
},
}
`);
});
it.todo("should be able to set selected team id, updating the selected team object");
});
describe("with server access", () => {
it("should be able to read own user", async ({ expect }) => {
await Auth.Otp.signIn();
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "server",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should be able to update own user", async ({ expect }) => {
await Auth.Otp.signIn();
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "server",
method: "PATCH",
body: {
display_name: "John Doe",
},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": "John Doe",
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should be able to delete own user", async ({ expect }) => {
await Auth.Otp.signIn();
const response = await niceBackendFetch("/api/v1/users/me", {
accessType: "server",
method: "DELETE",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": { "success": true },
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it.todo("should be able to list users");
it("should be able to read a user", async ({ expect }) => {
await Auth.Otp.signIn();
const signedInResponse = (await niceBackendFetch("/api/v1/users/me", {
accessType: "server",
}));
const userId = signedInResponse.body.id;
backendContext.set({
userAuth: null,
});
const response = await niceBackendFetch("/api/v1/users/" + userId, {
accessType: "server",
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
expect(response.body.primary_email).toEqual(backendContext.value.mailbox.emailAddress);
});
it("should be able to create a user", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 201,
"body": {
"auth_methods": [],
"auth_with_email": false,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": null,
"primary_email_verified": false,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should be able to create a user with email auth enabled", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: backendContext.value.mailbox.emailAddress,
primary_email_auth_enabled: true,
display_name: "John Dough",
server_metadata: "test",
},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 201,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": "John Dough",
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": false,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": "test",
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should be able to create a user with a password and sign in with it", async ({ expect }) => {
const password = generateSecureRandomString();
const response = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: backendContext.value.mailbox.emailAddress,
primary_email_auth_enabled: true,
password,
},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 201,
"body": {
"auth_methods": [
{
"identifier": "<stripped UUID>@stack-generated.example.com",
"type": "password",
},
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": true,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": false,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
const signInResponse = await Auth.Password.signInWithEmail({ password });
expect(signInResponse.signInResponse).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"access_token": <stripped field 'access_token'>,
"refresh_token": <stripped field 'refresh_token'>,
"user_id": "<stripped UUID>",
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should not be able to create a user without primary email but with email auth enabled", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email_auth_enabled: true,
},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 400,
"body": "primary_email_auth_enabled cannot be true without primary_email",
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should not be able to create a user with password but email auth disabled", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: backendContext.value.mailbox.emailAddress,
password: "shouldn't work",
},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 400,
"body": "password cannot be set without primary_email_auth_enabled",
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should not be able to create a user with email auth enabled if the email already exists with email auth enabled", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: backendContext.value.mailbox.emailAddress,
primary_email_auth_enabled: true,
},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 201,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": false,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
const response2 = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: backendContext.value.mailbox.emailAddress,
primary_email_auth_enabled: true,
},
});
expect(response2).toMatchInlineSnapshot(`
NiceResponse {
"status": 400,
"body": {
"code": "USER_EMAIL_ALREADY_EXISTS",
"error": "User already exists.",
},
"headers": Headers {
"x-stack-known-error": "USER_EMAIL_ALREADY_EXISTS",
<some fields may have been hidden>,
},
}
`);
});
it("should be able to create a user with email auth enabled if the email already exists but without email auth enabled", async ({ expect }) => {
const response = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: backendContext.value.mailbox.emailAddress,
},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 201,
"body": {
"auth_methods": [],
"auth_with_email": false,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": false,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
const password = generateSecureRandomString();
const response2 = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: backendContext.value.mailbox.emailAddress,
primary_email_auth_enabled: true,
password: password,
},
});
expect(response2).toMatchInlineSnapshot(`
NiceResponse {
"status": 201,
"body": {
"auth_methods": [
{
"identifier": "<stripped UUID>@stack-generated.example.com",
"type": "password",
},
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": true,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": false,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
const signInResponse = await Auth.Password.signInWithEmail({ password });
expect(signInResponse.signInResponse).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"access_token": <stripped field 'access_token'>,
"refresh_token": <stripped field 'refresh_token'>,
"user_id": "<stripped UUID>",
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should be able to create a user with email auth disabled even if the email already exists with email auth enabled", async ({ expect }) => {
const password = generateSecureRandomString();
const response2 = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: backendContext.value.mailbox.emailAddress,
primary_email_auth_enabled: true,
password: password,
},
});
expect(response2).toMatchInlineSnapshot(`
NiceResponse {
"status": 201,
"body": {
"auth_methods": [
{
"identifier": "<stripped UUID>@stack-generated.example.com",
"type": "password",
},
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": true,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": false,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
const response = await niceBackendFetch("/api/v1/users", {
accessType: "server",
method: "POST",
body: {
primary_email: backendContext.value.mailbox.emailAddress,
},
});
expect(response).toMatchInlineSnapshot(`
NiceResponse {
"status": 201,
"body": {
"auth_methods": [],
"auth_with_email": false,
"client_metadata": null,
"connected_accounts": [],
"display_name": null,
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": false,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": null,
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
const signInResponse = await Auth.Password.signInWithEmail({ password });
expect(signInResponse.signInResponse).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"access_token": <stripped field 'access_token'>,
"refresh_token": <stripped field 'refresh_token'>,
"user_id": "<stripped UUID>",
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should be able to update a user", async ({ expect }) => {
await Auth.Otp.signIn();
const signedInResponse = (await niceBackendFetch("/api/v1/users/me", {
accessType: "server",
}));
const userId = signedInResponse.body.id;
backendContext.set({
userAuth: null,
});
const response1 = await niceBackendFetch("/api/v1/users/" + userId, {
accessType: "server",
method: "PATCH",
body: {
display_name: "John Doe",
server_metadata: { key: "value" },
},
});
expect(response1).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": "John Doe",
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": { "key": "value" },
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
const response2 = await niceBackendFetch("/api/v1/users/" + userId, {
accessType: "server",
});
expect(response2).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": {
"auth_methods": [
{
"contact_channel": {
"email": "<stripped UUID>@stack-generated.example.com",
"type": "email",
},
"type": "otp",
},
],
"auth_with_email": true,
"client_metadata": null,
"connected_accounts": [],
"display_name": "John Doe",
"has_password": false,
"id": "<stripped UUID>",
"oauth_providers": [],
"primary_email": "<stripped UUID>@stack-generated.example.com",
"primary_email_verified": true,
"profile_image_url": null,
"selected_team": null,
"selected_team_id": null,
"server_metadata": { "key": "value" },
"signed_up_at_millis": <stripped field 'signed_up_at_millis'>,
},
"headers": Headers { <some fields may have been hidden> },
}
`);
});
it("should be able to delete a user", async ({ expect }) => {
await Auth.Otp.signIn();
const signedInResponse = (await niceBackendFetch("/api/v1/users/me", {
accessType: "server",
}));
const userId = signedInResponse.body.id;
backendContext.set({
userAuth: null,
});
const response1 = await niceBackendFetch("/api/v1/users/" + userId, {
accessType: "server",
method: "DELETE",
});
expect(response1).toMatchInlineSnapshot(`
NiceResponse {
"status": 200,
"body": { "success": true },
"headers": Headers { <some fields may have been hidden> },
}
`);
const response2 = await niceBackendFetch("/api/v1/users/" + userId, {
accessType: "server",
});
expect(response2).toMatchInlineSnapshot(`
NiceResponse {
"status": 404,
"body": {
"code": "USER_NOT_FOUND",
"error": "User not found.",
},
"headers": Headers {
"x-stack-known-error": "USER_NOT_FOUND",
<some fields may have been hidden>,
},
}
`);
});
});