Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions cypress/e2e/tests/auth/signup.cy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/* eslint-disable no-undef */
/// <reference types="cypress" />

import locators from "../../../support/locators";

describe("Sign Up tests", () => {
const TIMEOUT = {
DEFAULT: 10000,
API: 30000,
};

const testUser = {
name: "Test User",
email: `test${Date.now()}@aletheiafact.org`,
password: "TestPassword123!",
};

beforeEach(() => {
cy.clearCookies();
cy.clearLocalStorage();

cy.goToSignUpPage();

cy.get('iframe[title="reCAPTCHA"]', { timeout: TIMEOUT.DEFAULT })
.should("be.visible")
.its("0.contentDocument.body")
.should("not.be.empty");
});

it("Should display sign up form", () => {
cy.get(locators.signup.NAME).should("be.visible");
cy.get(locators.signup.EMAIL).should("be.visible");
cy.get(locators.signup.PASSWORD).should("be.visible");
cy.get(locators.signup.REPEATED_PASSWORD).should("be.visible");
cy.get('iframe[title="reCAPTCHA"]').should("be.visible");
cy.get(locators.signup.BTN_SUBMIT).should("be.visible");
});

it("Should show validation error when name is empty", () => {
cy.get(locators.signup.EMAIL).clear().type(testUser.email);
cy.get(locators.signup.PASSWORD).clear().type(testUser.password);
cy.get(locators.signup.REPEATED_PASSWORD)
.clear()
.type(testUser.password);
cy.checkRecaptcha();
cy.get(locators.signup.BTN_SUBMIT).click();

cy.get(locators.signup.ERROR_NAME, { timeout: TIMEOUT.DEFAULT }).should(
"be.visible"
);
});

it("Should show validation error when email is invalid", () => {
cy.get(locators.signup.NAME).clear().type(testUser.name);
cy.get(locators.signup.EMAIL).clear().type("invalid-email");
cy.get(locators.signup.PASSWORD).clear().type(testUser.password);
cy.get(locators.signup.REPEATED_PASSWORD)
.clear()
.type(testUser.password);
cy.checkRecaptcha();
cy.get(locators.signup.BTN_SUBMIT).click();

cy.get(locators.signup.ERROR_EMAIL, {
timeout: TIMEOUT.DEFAULT,
}).should("be.visible");
});

it("Should show validation error when passwords don't match", () => {
cy.get(locators.signup.NAME).clear().type(testUser.name);
cy.get(locators.signup.EMAIL).clear().type(testUser.email);
cy.get(locators.signup.PASSWORD).clear().type(testUser.password);
cy.get(locators.signup.REPEATED_PASSWORD)
.clear()
.type("DifferentPassword123!");
cy.checkRecaptcha();
cy.get(locators.signup.BTN_SUBMIT).click();

cy.get(locators.signup.ERROR_REPEATED_PASSWORD, {
timeout: TIMEOUT.DEFAULT,
}).should("be.visible");
});

it("Should show error when CAPTCHA is not completed", () => {
cy.get(locators.signup.NAME).clear().type(testUser.name);
cy.get(locators.signup.EMAIL).clear().type(testUser.email);
cy.get(locators.signup.PASSWORD).clear().type(testUser.password);
cy.get(locators.signup.REPEATED_PASSWORD)
.clear()
.type(testUser.password);
cy.get(locators.signup.BTN_SUBMIT).click();

cy.get(".ant-message-error, .MuiAlert-standardError, [role='alert']", {
timeout: TIMEOUT.DEFAULT,
}).should("be.visible");
});

it("Should successfully create account with valid data and CAPTCHA", () => {
cy.get(locators.signup.NAME).clear().type(testUser.name);
cy.get(locators.signup.EMAIL).clear().type(testUser.email);
cy.get(locators.signup.PASSWORD).clear().type(testUser.password);
cy.get(locators.signup.REPEATED_PASSWORD)
.clear()
.type(testUser.password);
cy.checkRecaptcha();

cy.intercept("POST", "/api/user/register", (req) => {
req.continue((res) => {
if (res.statusCode >= 400) {
cy.log(
`Registration failed: ${
res.statusCode
} - ${JSON.stringify(res.body)}`
);
}
});
}).as("registerUser");
cy.intercept("/api/.ory/sessions/whoami").as("confirmLogin");

cy.get(locators.signup.BTN_SUBMIT).click();

cy.wait("@registerUser", { timeout: TIMEOUT.API }).then(
(interception) => {
const isSuccess = [200, 201].includes(
interception.response.statusCode
);
if (!isSuccess) {
cy.log(
`Registration API Error: ${interception.response.statusCode}`
);
cy.log(
`Error body: ${JSON.stringify(
interception.response.body
)}`
);
expect(interception.response.statusCode).to.be.oneOf([
200, 201,
]);
}
}
);

cy.wait("@confirmLogin", { timeout: TIMEOUT.API });

cy.url({ timeout: TIMEOUT.API }).should("eq", "http://localhost:3000/");
cy.get(
".ant-message-success, .MuiAlert-standardSuccess, [role='alert']",
{ timeout: TIMEOUT.DEFAULT }
).should("exist");
});
});
33 changes: 31 additions & 2 deletions cypress/support/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,50 @@ Cypress.Commands.add("checkRecaptcha", () => {
// get the iframe > document > body
// and retries until the body is not empty or fails with timeout
return cy
.get('iframe[title="reCAPTCHA"]')
.get('iframe[title="reCAPTCHA"]', { timeout: 10000 })
.its("0.contentDocument.body")
.should("not.be.empty")
.then(cy.wrap);
};

getIframeBody().find("#recaptcha-anchor").click();
getIframeBody()
.find("#recaptcha-anchor", { timeout: 10000 })
.should("be.visible")
.click({ force: true });

cy.wait(500);
});

Cypress.Commands.add("goToSignUpPage", () => {
cy.visit("http://localhost:3000/sign-up");
cy.url().should("contains", "sign-up");
});

Cypress.Commands.add(
"signup",
(name: string, email: string, password: string) => {
cy.goToSignUpPage();
cy.get(locators.signup.NAME).type(name);
cy.get(locators.signup.EMAIL).type(email);
cy.get(locators.signup.PASSWORD).type(password);
cy.get(locators.signup.REPEATED_PASSWORD).type(password);
cy.checkRecaptcha();
cy.get(locators.signup.BTN_SUBMIT).click();
}
);

declare global {
namespace Cypress {
interface Chainable {
login(): Chainable<Element>;
checkRecaptcha(): Chainable<Element>;
goToLoginPage(): Chainable<Element>;
goToSignUpPage(): Chainable<Element>;
signup(
name: string,
email: string,
password: string
): Chainable<Element>;
}
}
}
12 changes: 12 additions & 0 deletions cypress/support/locators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,18 @@ const locators = {
BTN_LOGIN: "[data-cy=loginButton]",
},

signup: {
NAME: "[data-cy=nameInputCreateAccount]",
EMAIL: "[data-cy=emailInputCreateAccount]",
PASSWORD: "[data-cy=passwordInputCreateAccount]",
REPEATED_PASSWORD: "[data-cy=repeatedPasswordInputCreateAccount]",
BTN_SUBMIT: "[data-cy=loginButton]",
ERROR_NAME: "[data-cy=nameError]",
ERROR_EMAIL: "[data-cy=emailError]",
ERROR_PASSWORD: "[data-cy=passwordError]",
ERROR_REPEATED_PASSWORD: "[data-cy=repeatedPasswordError]",
},

personality: {
BTN_SEE_MORE_PERSONALITY: "[data-cy=testSeeMorePersonality]",
BTN_ADD_PERSONALITY: "[data-cy=testButtonCreatePersonality]",
Expand Down
8 changes: 5 additions & 3 deletions cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["es5", "dom"],
"types": ["cypress"]
"target": "es2017",
"lib": ["es2017", "dom"],
"types": ["cypress"],
"moduleResolution": "node",
"esModuleInterop": true
},
"include": ["**/*.ts"]
}
1 change: 1 addition & 0 deletions public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"supportEmail": "support@aletheifact.org",
"contactEmail": "contact@aletheiafact.org",
"captchaError": "There was an error validating the captcha",
"captchaLabel": "Verification",
"change": "Change",
"approve": "Approve",
"reject": "Reject"
Expand Down
1 change: 1 addition & 0 deletions public/locales/en/login.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"nameLabel": "Name",
"submitButton": "Submit",
"emailErrorMessage": "Please, insert your e-mail",
"invalidEmailErrorMessage": "Invalid e-mail",
"nameErrorMessage": "Please, insert your name",
"passwordErrorMessage": "Please, insert your password",
"loginFailedMessage": "Error while logging in",
Expand Down
1 change: 1 addition & 0 deletions public/locales/pt/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"supportEmail": "support@aletheifact.org",
"contactEmail": "contato@aletheiafact.org",
"captchaError": "Erro na validação do captcha",
"captchaLabel": "Verificação",
"change": "Mudar",
"approve": "Aprovar",
"reject": "Rejeitar"
Expand Down
5 changes: 5 additions & 0 deletions server/users/dto/create-user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ export class CreateUserDTO {
@IsString()
@ApiProperty()
password: string;

@IsNotEmpty()
@IsString()
@ApiProperty()
recaptcha: string;
}
14 changes: 12 additions & 2 deletions server/users/users.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { ApiTags } from "@nestjs/swagger";
import { UtilService } from "../util";
import { GetUsersDTO } from "./dto/get-users.dto";
import { Public, AdminOnly, Auth } from "../auth/decorators/auth.decorator";
import { CaptchaService } from "../captcha/captcha.service";

// TODO: check permissions for routes
@Controller(":namespace?")
Expand All @@ -33,7 +34,8 @@ export class UsersController {
private readonly usersService: UsersService,
private viewService: ViewService,
private configService: ConfigService,
private util: UtilService
private readonly util: UtilService,
private readonly captchaService: CaptchaService
) {}

@ApiTags("pages")
Expand All @@ -52,18 +54,26 @@ export class UsersController {
@Public()
public async signUp(@Req() req: Request, @Res() res: Response) {
const parsedUrl = parse(req.url, true);
const sitekey = this.configService.get<string>("recaptcha_sitekey");
await this.viewService.render(
req,
res,
"/sign-up",
Object.assign(parsedUrl.query)
Object.assign(parsedUrl.query, { sitekey })
);
}

@ApiTags("user")
@Post("api/user/register")
@Public()
public async register(@Body() createUserDto: CreateUserDTO) {
const validateCaptcha = await this.captchaService.validate(
createUserDto.recaptcha
);
if (!validateCaptcha) {
throw new UnprocessableEntityException("Error validating captcha");
}

try {
return await this.usersService.register(createUserDto);
} catch (errorResponse) {
Expand Down
4 changes: 3 additions & 1 deletion server/users/users.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { UtilService } from "../util";
import { NotificationModule } from "../notifications/notifications.module";
import { SessionGuard } from "../auth/session.guard";
import { M2MGuard } from "../auth/m2m.guard";
import { CaptchaModule } from "../captcha/captcha.module";

const UserModel = MongooseModule.forFeature([
{
Expand All @@ -26,7 +27,8 @@ const UserModel = MongooseModule.forFeature([
OryModule,
ConfigModule,
AbilityModule,
NotificationModule
NotificationModule,
CaptchaModule,
],
exports: [UsersService, UserModel],
controllers: [UsersController],
Expand Down
19 changes: 14 additions & 5 deletions src/components/Login/LoginView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,10 @@ const LoginView = ({ isSignUp = false, shouldGoBack = false }) => {
if (err.response?.status === 400) {
// Yup, it is!
setFlow(err.response?.data);
return MessageManager.showMessage("error", `${t("profile:totpIncorectCodeMessage")}`);
return MessageManager.showMessage(
"error",
`${t("profile:totpIncorectCodeMessage")}`
);
}

return Promise.reject(err);
Expand Down Expand Up @@ -122,6 +125,7 @@ const LoginView = ({ isSignUp = false, shouldGoBack = false }) => {
email,
password,
name: values.name,
recaptcha: values.recaptcha,
};
userApi.register(payload, t).then((res) => {
if (!res?.error) {
Expand All @@ -143,15 +147,19 @@ const LoginView = ({ isSignUp = false, shouldGoBack = false }) => {

const onFinishFailed = (errorInfo) => {
if (typeof errorInfo === "string") {
MessageManager.showMessage("error", errorInfo)
MessageManager.showMessage("error", errorInfo);
} else {
MessageManager.showMessage("error", `${t("login:loginFailedMessage")}`);
MessageManager.showMessage(
"error",
`${t("login:loginFailedMessage")}`
);
}
setIsLoading(false);
};

return (
<Grid container
<Grid
container
justifyContent="center"
style={{ marginTop: 45, height: "100%", padding: "24px" }}
>
Expand All @@ -176,7 +184,8 @@ const LoginView = ({ isSignUp = false, shouldGoBack = false }) => {
</Grid>
<CTAButton
type={ButtonType.blue}
textWhenLoggedOut={t("CTAFolder:button")} />
textWhenLoggedOut={t("CTAFolder:button")}
/>
</>
)}
</Grid>
Expand Down
Loading
Loading