Skip to content
Open
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
25 changes: 0 additions & 25 deletions .eslintrc.cjs

This file was deleted.

2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
v20.13.1
v22.14.0
25 changes: 19 additions & 6 deletions amplify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ test:
phases:
preTest:
commands:
- npm ci --verbose
- npm install -g pm2 --verbose
- npm install -g wait-on --verbose
- npm install mocha mochawesome --verbose
- npm install mochawesome-merge --verbose
- npm install -g mochawesome-report-generator --verbose
- npm ci
- npm install -g pm2
- npm install -g wait-on
- npm install mocha mochawesome
- npm install mochawesome-merge
- npm install -g mochawesome-report-generator

# Run the Cognito user management script with ts-node
- echo "Running Cognito User Setup Script..."

- node amplify/cicd/cognito-setup.mjs || { echo "Cognito setup failed!
Exiting..."; exit 1; }

# Start the frontend server
- pm2 start npm -- run dev
- wait-on http://localhost:5173
test:
Expand All @@ -29,6 +37,11 @@ test:
- "**/*.mp4"
backend:
phases:
preBuild:
commands:
- nvm install $(cat .nvmrc) && nvm use $(cat .nvmrc)
- node -v
- npm ci --verbose
build:
commands:
- npm ci --cache .npm --prefer-offline
Expand Down
21 changes: 21 additions & 0 deletions amplify/auth/userAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,25 @@ const userAttributes = {
phoneNumber: { order: 4, required: false },
};

export const extendedUserAttributeOptions = [
"name",
"family_name",
"given_name",
"middle_name",
"nickname",
"preferred_username",
"profile",
"picture",
"website",
"gender",
"birthdate",
"zoneinfo",
"locale",
"updated_at",
"address",
"email",
"phone_number",
"sub",
];

export default userAttributes;
9 changes: 9 additions & 0 deletions amplify/cicd/.atg.tester.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
[
{
"username": "avael.us+1@gmail.com",
"password": "Test123!",
"givenName": "Test",
"familyName": "User",
"kind": "test"
}
]
127 changes: 127 additions & 0 deletions amplify/cicd/atg-cognito.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {
CognitoIdentityProviderClient,
AdminConfirmSignUpCommand,
AdminUpdateUserAttributesCommand,
AdminDeleteUserCommand,
AdminGetUserCommand,
} from "@aws-sdk/client-cognito-identity-provider";

import { signUp } from "aws-amplify/auth";

import { promises as fs } from "fs";

export class AtgCognito {
cognito;
amplifyOutputs;

constructor(amplifyOutputs) {
this.amplifyOutputs = amplifyOutputs;
this.cognito = new CognitoIdentityProviderClient({
region: this.amplifyOutputs.auth.aws_region,
});
console.log(
"AtgCognito initialized with region:",
this.amplifyOutputs.auth.aws_region
);
}

async createUser(username, password, givenName, familyName) {
try {
const { isSignUpComplete, userId, nextStep } = await signUp({
username,
password,
options: {
userAttributes: {
email: username,
given_name: givenName,
family_name: familyName,
},
},
});

console.log(`🔑 isSignUpComplete: ${JSON.stringify(isSignUpComplete)}`);
console.log(`👤 userId: ${JSON.stringify(userId)}`);
console.log(`🔄 nextStep: ${JSON.stringify(nextStep)}`);

return String(userId);
} catch (error) {
console.error("🚨 Error during user signup:", error);
throw error;
}
}

async deleteUser(username) {
const params = {
UserPoolId: this.amplifyOutputs.auth.user_pool_id,
Username: username,
};

try {
const getUserCommand = new AdminGetUserCommand(params);
// ✅ Step 1: Check if the user exists
await this.cognito.send(getUserCommand);

// ✅ Step 2: If user exists, proceed with deletion
const deleteCommand = new AdminDeleteUserCommand(params);
await this.cognito.send(deleteCommand);
console.log(`🗑️ User ${username} deleted successfully.`);
} catch (error) {
if (error.name === "UserNotFoundException") {
console.warn(`⚠️ User ${username} does not exist. Skipping delete.`);
return;
}
console.error("🚨 Error deleting user:", error);
throw error; // If it's another error, rethrow it
}
}

async confirmUser(username) {
const params = {
UserPoolId: this.amplifyOutputs.auth.user_pool_id,
Username: username,
};
const command = new AdminConfirmSignUpCommand(params);

console.log("Sending AdminConfirmSignUpCommand with:", params);
try {
await this.cognito.send(command);
console.log(`User ${username} has been confirmed successfully.`);
} catch (error) {
console.error("Error confirming user:", error);
}
}

async verifyUserEmail(username) {
const params = {
UserPoolId: this.amplifyOutputs.auth.user_pool_id,
Username: username,
UserAttributes: [
{
Name: "email_verified",
Value: "true",
},
],
};
const command = new AdminUpdateUserAttributesCommand(params);

console.log("Sending AdminUpdateUserAttributesCommand with:", params);
try {
await this.cognito.send(command);
console.log(`Email for user ${username} has been verified successfully.`);
} catch (error) {
console.error("Error verifying email:", error);
}
}

async readCredentialsFile(filePath) {
try {
const fileContent = await fs.readFile(filePath, "utf-8");
return JSON.parse(fileContent);
} catch (error) {
console.error(
`Failed to read the credentials file. Error: ${error.message}`
);
return [];
}
}
}
35 changes: 35 additions & 0 deletions amplify/cicd/cognito-setup.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Amplify } from "aws-amplify";
import amplifyOutputs from "../../amplify_outputs.json" assert { type: "json" };
import { AtgCognito } from "./atg-cognito.mjs";

// Configure Amplify with the outputs
Amplify.configure(amplifyOutputs);

const atgCognito = new AtgCognito(amplifyOutputs);

async function setupCognitoUsers() {
const testCredentials = await atgCognito.readCredentialsFile(
new URL("./.atg.tester.json", import.meta.url)
);

if (testCredentials.length > 0) {
for (const cred of testCredentials) {
const { username, password, givenName, familyName } = cred;
console.log("✅", JSON.stringify(cred));

try {
await atgCognito.deleteUser(username);
await atgCognito.createUser(username, password, givenName, familyName);
await atgCognito.verifyUserEmail(username);
await atgCognito.confirmUser(username);
} catch (error) {
console.error(`❌ Error creating or confirming user: ${error}`);
process.exit(1);
}
}
} else {
console.warn("⚠️ No test users found!");
}
}

setupCognitoUsers();
2 changes: 1 addition & 1 deletion cypress/tests/ui/integration/authenticator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ describe("Authenticator:", function () {
});

describe("Sign In:", () => {
it("allows a user to signin", () => {
it("allows a user to login and logout", () => {
// Step 2: Take an action (Sign in)
cy.get(selectors.emailInput).type("avael.us+1@gmail.com");
cy.get(selectors.signInPasswordInput).type("Test123!");
Expand Down
8 changes: 7 additions & 1 deletion cypress/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
{
"extends": "../tsconfig.json",
"include": ["./**/*.ts", "../cypress.d.ts"],
"include": [
"../amplify/auth/.atg.tester.json",
"./**/*.ts",
"../cypress.d.ts",
"atg-cognito.js",
"cognito-setup.js"
],
"exclude": [],
"compilerOptions": {
"types": ["cypress", "@percy/cypress"],
Expand Down
92 changes: 92 additions & 0 deletions eslint.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import js from "@eslint/js";
import ts from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import reactHooks from "eslint-plugin-react-hooks";
import tailwindcss from "eslint-plugin-tailwindcss";
import reactRefresh from "eslint-plugin-react-refresh";

export default [
{
ignores: [
"dist",
".eslintrc.cjs",
"src/_DEV",
"api/client/graphql",
"src/components/generated",
"cypress",
".amplify",
],
},
{
languageOptions: {
ecmaVersion: 2020,
sourceType: "module",
parser: tsParser,
globals: {
// Browser Globals
window: "readonly",
document: "readonly",
HTMLElement: "readonly",
HTMLAnchorElement: "readonly",
KeyboardEvent: "readonly",
WheelEvent: "readonly",
AbortController: "readonly",

// Node.js Globals
process: "readonly",
global: "readonly",
setTimeout: "readonly",
clearTimeout: "readonly",
console: "readonly",
URL: "readonly",
structuredClone: "readonly",
Buffer: "readonly",

// CommonJS Globals
module: "readonly",
exports: "readonly",
require: "readonly",

// React & JSX
React: "readonly",
JSX: "readonly",
VoidFunction: "readonly",
},
},
linterOptions: {
reportUnusedDisableDirectives: true,
},
plugins: {
"@typescript-eslint": ts,
"react-hooks": reactHooks,
tailwindcss: tailwindcss,
"react-refresh": reactRefresh,
},
rules: {
...js.configs.recommended.rules,
...ts.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
...tailwindcss.configs.recommended.rules,

"react-refresh/only-export-components": [
"warn",
{ allowConstantExport: true },
],

// Avoid unexpected expression errors
"@typescript-eslint/no-unused-expressions": "off",

// Allow unused vars for debugging
"@typescript-eslint/no-unused-vars": [
"warn",
{ argsIgnorePattern: "^_" },
],

// Fix unsafe optional chaining errors
"no-unsafe-optional-chaining": "off",

// Require break statements in switch cases
"no-fallthrough": "error",
},
},
];
Loading