diff --git a/frontend/components/App/Header.vue b/frontend/components/App/Header.vue index 678e1be..176aa04 100644 --- a/frontend/components/App/Header.vue +++ b/frontend/components/App/Header.vue @@ -64,7 +64,7 @@ -
+
diff --git a/frontend/components/Base/Card.vue b/frontend/components/Base/Card.vue new file mode 100644 index 0000000..0392784 --- /dev/null +++ b/frontend/components/Base/Card.vue @@ -0,0 +1,16 @@ + diff --git a/frontend/components/Label/CreateModal.vue b/frontend/components/Label/CreateModal.vue index 823a10a..99bfaa9 100644 --- a/frontend/components/Label/CreateModal.vue +++ b/frontend/components/Label/CreateModal.vue @@ -9,7 +9,7 @@ :autofocus="true" label="Label Name" /> - + diff --git a/frontend/components/Location/CreateModal.vue b/frontend/components/Location/CreateModal.vue index f854c3c..5d9713a 100644 --- a/frontend/components/Location/CreateModal.vue +++ b/frontend/components/Location/CreateModal.vue @@ -9,7 +9,7 @@ :autofocus="true" label="Location Name" /> - + diff --git a/frontend/components/global/DetailsSection/DetailsSection.vue b/frontend/components/global/DetailsSection/DetailsSection.vue new file mode 100644 index 0000000..dc56fcc --- /dev/null +++ b/frontend/components/global/DetailsSection/DetailsSection.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/components/global/DetailsSection/index.ts b/frontend/components/global/DetailsSection/index.ts new file mode 100644 index 0000000..82f0782 --- /dev/null +++ b/frontend/components/global/DetailsSection/index.ts @@ -0,0 +1,2 @@ +import DetailsSection from "./DetailsSection.vue"; +export default DetailsSection; diff --git a/frontend/components/global/DetailsSection/types.ts b/frontend/components/global/DetailsSection/types.ts new file mode 100644 index 0000000..ab4364c --- /dev/null +++ b/frontend/components/global/DetailsSection/types.ts @@ -0,0 +1,15 @@ +export type StringLike = string | number | boolean; + +export type DateDetail = { + name: string; + text: string | Date; + slot?: string; + type: "date"; +}; + +export type Detail = { + name: string; + text: StringLike; + slot?: string; + type?: "text"; +}; diff --git a/frontend/components/global/PasswordScore.vue b/frontend/components/global/PasswordScore.vue new file mode 100644 index 0000000..1394237 --- /dev/null +++ b/frontend/components/global/PasswordScore.vue @@ -0,0 +1,40 @@ + + + + + diff --git a/frontend/composables/use-confirm.ts b/frontend/composables/use-confirm.ts index 2055d06..9c408ff 100644 --- a/frontend/composables/use-confirm.ts +++ b/frontend/composables/use-confirm.ts @@ -1,10 +1,11 @@ -import { UseConfirmDialogReturn } from "@vueuse/core"; +import { UseConfirmDialogRevealResult, UseConfirmDialogReturn } from "@vueuse/core"; import { Ref } from "vue"; // eslint-disable-next-line @typescript-eslint/no-explicit-any type Store = UseConfirmDialogReturn & { text: Ref; setup: boolean; + open: (text: string) => Promise>; }; const store: Partial = { @@ -30,13 +31,13 @@ export function useConfirm(): Store { store.cancel = cancel; } - async function openDialog(msg: string) { + async function openDialog(msg: string): Promise> { store.text.value = msg; return await store.reveal(); } return { ...(store as Store), - reveal: openDialog, + open: openDialog, }; } diff --git a/frontend/composables/use-password-score.ts b/frontend/composables/use-password-score.ts new file mode 100644 index 0000000..fcb7621 --- /dev/null +++ b/frontend/composables/use-password-score.ts @@ -0,0 +1,37 @@ +import type { ComputedRef, Ref } from "vue"; +import { scorePassword } from "~~/lib/passwords"; + +export interface PasswordScore { + score: ComputedRef; + message: ComputedRef; + isValid: ComputedRef; +} + +export function usePasswordScore(pw: Ref, min = 30): PasswordScore { + const score = computed(() => { + return scorePassword(pw.value) || 0; + }); + + const message = computed(() => { + if (score.value < 20) { + return "Very weak"; + } else if (score.value < 40) { + return "Weak"; + } else if (score.value < 60) { + return "Good"; + } else if (score.value < 80) { + return "Strong"; + } + return "Very strong"; + }); + + const isValid = computed(() => { + return score.value >= min; + }); + + return { + score, + isValid, + message, + }; +} diff --git a/frontend/lib/api/user.ts b/frontend/lib/api/user.ts index e55bab9..7a6b122 100644 --- a/frontend/lib/api/user.ts +++ b/frontend/lib/api/user.ts @@ -2,19 +2,13 @@ import { BaseAPI, route } from "./base"; import { ItemsApi } from "./classes/items"; import { LabelsApi } from "./classes/labels"; import { LocationsApi } from "./classes/locations"; +import { UserOut } from "./types/data-contracts"; import { Requests } from "~~/lib/requests"; export type Result = { item: T; }; -export type User = { - name: string; - email: string; - isSuperuser: boolean; - id: number; -}; - export class UserApi extends BaseAPI { locations: LocationsApi; labels: LabelsApi; @@ -30,7 +24,7 @@ export class UserApi extends BaseAPI { } public self() { - return this.http.get>({ url: route("/users/self") }); + return this.http.get>({ url: route("/users/self") }); } public logout() { diff --git a/frontend/lib/passwords/index.test.ts b/frontend/lib/passwords/index.test.ts new file mode 100644 index 0000000..50c1b52 --- /dev/null +++ b/frontend/lib/passwords/index.test.ts @@ -0,0 +1,30 @@ +import { describe, test, expect } from "vitest"; +import { scorePassword } from "."; + +describe("scorePassword tests", () => { + test("flagged words should return negative number", () => { + const flaggedWords = ["password", "homebox", "admin", "qwerty", "login"]; + + for (const word of flaggedWords) { + expect(scorePassword(word)).toBe(0); + } + }); + + test("should return 0 for empty string", () => { + expect(scorePassword("")).toBe(0); + }); + + test("should return 0 for strings less than 6", () => { + expect(scorePassword("12345")).toBe(0); + }); + + test("should return positive number for long string", () => { + const result = expect(scorePassword("123456")); + result.toBeGreaterThan(0); + result.toBeLessThan(31); + }); + + test("should return max number for long string with all variations", () => { + expect(scorePassword("3bYWcfYOwqxljqeOmQXTLlBwkrH6HV")).toBe(100); + }); +}); diff --git a/frontend/lib/passwords/index.ts b/frontend/lib/passwords/index.ts new file mode 100644 index 0000000..27a3c45 --- /dev/null +++ b/frontend/lib/passwords/index.ts @@ -0,0 +1,45 @@ +const flaggedWords = ["password", "homebox", "admin", "qwerty", "login"]; + +/** + * scorePassword returns a score for a given password between 0 and 100. + * if a password contains a flagged word, it returns 0. + * @param pass + * @returns + */ +export function scorePassword(pass: string): number { + let score = 0; + if (!pass) return score; + + if (pass.length < 6) return score; + + // Check for flagged words + for (const word of flaggedWords) { + if (pass.toLowerCase().includes(word)) { + return 0; + } + } + + // award every unique letter until 5 repetitions + const letters: { [key: string]: number } = {}; + + for (let i = 0; i < pass.length; i++) { + letters[pass[i]] = (letters[pass[i]] || 0) + 1; + score += 5.0 / letters[pass[i]]; + } + + // bonus points for mixing it up + const variations: { [key: string]: boolean } = { + digits: /\d/.test(pass), + lower: /[a-z]/.test(pass), + upper: /[A-Z]/.test(pass), + nonWords: /\W/.test(pass), + }; + + let variationCount = 0; + for (const check in variations) { + variationCount += variations[check] === true ? 1 : 0; + } + score += (variationCount - 1) * 10; + + return Math.max(Math.min(score, 100), 0); +} diff --git a/frontend/lib/strings/index.test.ts b/frontend/lib/strings/index.test.ts index 1f266ea..e07faee 100644 --- a/frontend/lib/strings/index.test.ts +++ b/frontend/lib/strings/index.test.ts @@ -1,56 +1,56 @@ -import { describe, it, expect } from "vitest"; +import { describe, test, expect } from "vitest"; import { titlecase, capitalize, truncate } from "."; describe("title case tests", () => { - it("should return the same string if it's already title case", () => { + test("should return the same string if it's already title case", () => { expect(titlecase("Hello World")).toBe("Hello World"); }); - it("should title case a lower case word", () => { + test("should title case a lower case word", () => { expect(titlecase("hello")).toBe("Hello"); }); - it("should title case a sentence", () => { + test("should title case a sentence", () => { expect(titlecase("hello world")).toBe("Hello World"); }); - it("should title case a sentence with multiple words", () => { + test("should title case a sentence with multiple words", () => { expect(titlecase("hello world this is a test")).toBe("Hello World This Is A Test"); }); }); describe("capitilize tests", () => { - it("should return the same string if it's already capitalized", () => { + test("should return the same string if it's already capitalized", () => { expect(capitalize("Hello")).toBe("Hello"); }); - it("should capitalize a lower case word", () => { + test("should capitalize a lower case word", () => { expect(capitalize("hello")).toBe("Hello"); }); - it("should capitalize a sentence", () => { + test("should capitalize a sentence", () => { expect(capitalize("hello world")).toBe("Hello world"); }); - it("should capitalize a sentence with multiple words", () => { + test("should capitalize a sentence with multiple words", () => { expect(capitalize("hello world this is a test")).toBe("Hello world this is a test"); }); }); describe("truncase tests", () => { - it("should return the same string if it's already truncated", () => { + test("should return the same string if it's already truncated", () => { expect(truncate("Hello", 5)).toBe("Hello"); }); - it("should truncate a lower case word", () => { + test("should truncate a lower case word", () => { expect(truncate("hello", 3)).toBe("hel..."); }); - it("should truncate a sentence", () => { + test("should truncate a sentence", () => { expect(truncate("hello world", 5)).toBe("hello..."); }); - it("should truncate a sentence with multiple words", () => { + test("should truncate a sentence with multiple words", () => { expect(truncate("hello world this is a test", 10)).toBe("hello worl..."); }); }); diff --git a/frontend/package.json b/frontend/package.json index 4bb2f78..595b8d3 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,7 +20,7 @@ "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-vue": "^9.4.0", "isomorphic-fetch": "^3.0.0", - "nuxt": "3.0.0-rc.8", + "nuxt": "3.0.0-rc.11", "prettier": "^2.7.1", "typescript": "^4.8.3", "vite-plugin-eslint": "^1.8.1", diff --git a/frontend/pages/home.vue b/frontend/pages/home.vue index f39eb6b..be6363a 100644 --- a/frontend/pages/home.vue +++ b/frontend/pages/home.vue @@ -1,4 +1,5 @@ diff --git a/frontend/pages/index.vue b/frontend/pages/index.vue index 88031cc..99ec702 100644 --- a/frontend/pages/index.vue +++ b/frontend/pages/index.vue @@ -1,7 +1,4 @@