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
6 changes: 6 additions & 0 deletions api/bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions api/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'the-big-username-blacklist'
4 changes: 3 additions & 1 deletion api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"typescript": "^5.7.3"
},
"dependencies": {
"@2toad/profanity": "^3.1.1",
"@elysiajs/cors": "^1.2.0",
"@elysiajs/jwt": "^1.2.0",
"@sinclair/typebox": "^0.34.14",
Expand All @@ -38,6 +39,7 @@
"elysia": "^1.2.10",
"ky": "^1.7.4",
"luxon": "^3.5.0",
"pg": "^8.13.1"
"pg": "^8.13.1",
"the-big-username-blacklist": "^1.5.2"
}
}
22 changes: 16 additions & 6 deletions api/src/routes/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import {
signinResponse,
signUpBody,
signinBody,
podProviderEndpoint
podProviderEndpoint,
ApiSignUpErrors
} from '../types'
import { eq } from 'drizzle-orm'
import Elysia, { t } from 'elysia'
Expand Down Expand Up @@ -87,20 +88,29 @@ const authPlugin = new Elysia({ name: 'auth' })
)
.post(
'/signup',
async ({ body, error, headers: { auth }, jwt }) => {
async ({ body, error, headers: { auth }, jwt, profanity }) => {
// check if user is already logged in
if (auth && (await jwt.verify(auth))) {
return "You're already logged in"
}
const { username, password, email, providerName } = body

// check if username or email is profane
if ((profanity.exists(username), profanity.exists(email))) {
return error(400, ApiSignUpErrors.UsernameOrEmailContainsProfanity)
}
// check if username contains unwanted characters
const unwantedChars = new RegExp(/[@#/\\$%^&*!?<>+~=]/g)
if (unwantedChars.test(username)) {
return error(400, ApiSignUpErrors.UsernameInvalid)
}
// try to sign up the user with the current provider
try {
const providerResponse = await ActivityPod.signup(podProviderEndpoint[providerName], username, password, email)
let userResponse: SelectUsers[] = []

if (providerResponse.token === undefined) {
return error(400, 'Provider did not return a token')
return error(400, ApiSignUpErrors.ProviderToken)
} else {
// the provider created a new user, so we need to create a new user in the database
try {
Expand All @@ -121,7 +131,7 @@ const authPlugin = new Elysia({ name: 'auth' })
}
} catch (e) {
console.error('Error while checking if user is in the database: ', e)
return error(500, 'Error while checking user')
return error(500, ApiSignUpErrors.DBError)
}
const tokenObject = getTokenObject(new User(userResponse[0], providerResponse.token))
const authToken = await jwt.sign(tokenObject)
Expand All @@ -138,15 +148,15 @@ const authPlugin = new Elysia({ name: 'auth' })
return error(errorJson.code, errorJson.message)
}
console.error('Error while signing up the user', e)
return error(400, 'Error with the provider')
return error(400, ApiSignUpErrors.ProviderDefault)
}
},
{
detail: 'Signs up a new user',
body: signUpBody,
response: {
200: signinResponse,
400: t.String(),
400: t.Enum(ApiSignUpErrors),
500: t.String()
}
}
Expand Down
10 changes: 10 additions & 0 deletions api/src/routes/setup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Profanity } from '@2toad/profanity'
import User from '../decorater/User'
import cors from '@elysiajs/cors'
import jwt from '@elysiajs/jwt'
import Elysia from 'elysia'
import { list } from 'the-big-username-blacklist'

const setupPlugin = new Elysia({ name: 'setup' })
.use(
Expand All @@ -12,6 +14,14 @@ const setupPlugin = new Elysia({ name: 'setup' })
)
.use(cors())
.decorate('user', new User())
.decorate('profanity', () => {
const profanity = new Profanity({
languages: ['en', 'de', 'fr', 'ja', 'pt', 'es', 'ru', 'ar', 'ko'],
wholeWord: false
})
profanity.addWords(list)
return profanity
})
.macro({
isSignedIn: enabled => {
if (!enabled) return
Expand Down
1 change: 0 additions & 1 deletion api/src/routes/users.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ export default new Elysia({ name: 'user', prefix: '/user' })
}
return returnObject
} catch (_) {
console.log(_)
return error(400, FollowErrors.NotValidProvider)
}
},
Expand Down
25 changes: 25 additions & 0 deletions api/src/test/profanity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { describe, expect, it } from 'bun:test'
import { getProfanity } from '@/util'

describe('Check profanity module', () => {
it('should return false for strings without profanity', () => {
const notProfane = [
'Taiwan',
'Taiwanese',
'Taiwanesepeople',
'Protestant',
'Protestantism',
'Transgender',
'Hongkong',
'HongKong',
'HongKongpeople',
'Taiwanisafreecountry'
]

const profanity = getProfanity()

notProfane.forEach(word => {
expect(profanity.exists(word)).toBe(false)
})
})
})
8 changes: 8 additions & 0 deletions api/src/types/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ export enum FollowErrors {
IsSelf = 'Cannot follow yourself.'
}

export enum ApiSignUpErrors {
UsernameInvalid = 'Username is invalid',
UsernameOrEmailContainsProfanity = 'Username or email contains profanity',
ProviderToken = 'Provider did not return a token',
DBError = 'Error while checking user',
ProviderDefault = 'Error with the provider'
}

// Errors that are thrown by the pod provider
export enum ProviderSignUpErrors {
providerSignUpDefault = 'Error while signing up the user',
Expand Down
17 changes: 17 additions & 0 deletions api/src/util/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export * from './user'
import { Profanity } from '@2toad/profanity'
import { List } from '@2toad/profanity/dist/models'
import { list } from 'the-big-username-blacklist'

export function getProfanity() {
const whiteList = new List(() => true)
whiteList.addWords(['taiwan'])
const profanity = new Profanity({
languages: ['en', 'de', 'fr', 'ja', 'pt', 'es', 'ru', 'ar', 'ko'],
wholeWord: true
})
profanity.addWords(list)
profanity.whitelist = whiteList

return profanity
}
Binary file modified frontend/bun.lockb
Binary file not shown.
1 change: 1 addition & 0 deletions frontend/env.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference types="vite/client" />

declare module 'vue-iconsax'
declare module 'the-big-username-blacklist'

interface ImportMetaEnv {
readonly VITE_API_URL: string
Expand Down
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"open:ios": "cap open ios"
},
"dependencies": {
"@2toad/profanity": "^3.1.1",
"@capacitor/android": "^7.0.1",
"@capacitor/core": "^7.0.1",
"@capacitor/ios": "^7.0.1",
Expand All @@ -28,6 +29,7 @@
"luxon": "^3.5.0",
"pinia": "^2.3.1",
"tailwindcss": "^4.0.0",
"the-big-username-blacklist": "^1.5.2",
"vue": "^3.5.13",
"vue-iconsax": "^2.0.0",
"vue-router": "^4.5.0",
Expand Down
8 changes: 5 additions & 3 deletions frontend/src/components/SignupForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { useAuthStore } from '@/stores/authStore'
import MemoryButton from '@/components/MemoryButton.vue'
import MemoryInput from '@/components/MemoryInput.vue'
import { ref } from 'vue'
import { ProviderSignUpErrors } from '@/types'
import { ViablePodProvider } from '#api/types'
import { ViablePodProvider, ProviderSignUpErrors, ApiSignUpErrors } from '#api/types'

// Store
const authStore = useAuthStore()
Expand All @@ -31,7 +30,6 @@ async function submitForm() {
const authResponse = await authStore.signup(email.value, username.value, password.value, endpoint.value)
// if the response is a string, it means that there was an error
if (authResponse) {
console.log(authResponse)
switch (authResponse) {
case ProviderSignUpErrors['username.already.exists']:
case ProviderSignUpErrors['username.invalid']:
Expand All @@ -41,6 +39,10 @@ async function submitForm() {
case ProviderSignUpErrors['email.invalid']:
errorMessages.value.email = authResponse
break
case ApiSignUpErrors.UsernameOrEmailContainsProfanity:
case ApiSignUpErrors.UsernameInvalid:
errorMessages.value.username = authResponse
break
default:
errorMessages.value.default = authResponse
break
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/controller/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ export class ApiClient {
}
}
}
console.log('error when requesting api: ', e)
console.log(await e.response.text())
console.error('error when requesting api: ', e)
console.error(await e.response.text())
return {
data: ApiErrorsGeneral.default,
status: e.response.status
Expand Down
14 changes: 12 additions & 2 deletions frontend/src/controller/formValidation.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
import { getProfanity } from '#api/util'

const profanity = getProfanity()

export function validateUsername(username: string, required = true): string | undefined {
if (required && username === '') {
return 'Username is Required'
Expand All @@ -8,7 +12,13 @@ export function validateUsername(username: string, required = true): string | un
if (username.includes(' ')) {
return 'Username cannot contain spaces'
}
// TODO: check if username includes bad words
if (profanity.exists(username)) {
return 'Username is blacklisted'
}
const unwantedChars = new RegExp(/[@#/\\$%^&*!?<>+~=]/g)
if (unwantedChars.test(username)) {
return 'Username cannot contain the following characters: @ # \\ $ % ^ & * ! ? < > + ~ ='
}
return undefined
}

Expand All @@ -25,7 +35,7 @@ export function validateEmail(email: string, required = true): string | undefine
if (required && email === '') {
return 'Email is required'
}
if (emailRegex.exec(email) === null) {
if (!emailRegex.test(email)) {
return 'Invalid Email'
}
return undefined
Expand Down
25 changes: 18 additions & 7 deletions frontend/src/stores/authStore.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { SignUpBody, SignInResponse, ViablePodProvider, SelectUsers } from '#api/types'
import type { SignUpBody, ViablePodProvider, SelectUsers } from '#api/types'
import { ApiClient } from '@/controller/api'
import type { ApiErrors } from '@/types'
import { defineStore } from 'pinia'
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { VsNotification } from 'vuesax-alpha'

export const useAuthStore = defineStore('auth', () => {
// Default state
Expand Down Expand Up @@ -66,17 +67,22 @@ export const useAuthStore = defineStore('auth', () => {
try {
const { data: response, status } = await client.signin({ username, password, providerName })
if (status === 200) {
const signInResponse = response as SignInResponse
const signInResponse = response

setLoggedIn(true)
setToken(signInResponse.token)
setUser(signInResponse.user)
router.push({ name: 'home' })
} else if (status === 401) {
return response as ApiErrors
} else if (status === 401 || status === 400) {
VsNotification({
title: 'Wrong Credentials',
content: 'Please check your credentials',
color: 'danger'
})
return response
}
} catch (error) {
console.log('error when trying to signIn: ', error)
console.error('error when trying to signIn: ', error)
}
}
/**
Expand All @@ -95,13 +101,18 @@ export const useAuthStore = defineStore('auth', () => {
const body: SignUpBody = { username, password, email, providerName }
const { data: response, status } = await client.signup(body)
if (status === 200) {
const signupResponse = response as SignInResponse
const signupResponse = response
setLoggedIn(true)
setToken(signupResponse.token)
setUser(signupResponse.user)
router.push({ name: 'home' })
} else if (status === 500) {
return response as ApiErrors
VsNotification({
title: 'Error',
content: 'Something went wrong',
color: 'danger'
})
return response
}
}

Expand Down
4 changes: 2 additions & 2 deletions frontend/src/types/enums.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FollowErrors, ProviderSignInErrors, ProviderSignUpErrors } from '#api/types'
import type { ApiSignUpErrors, FollowErrors, ProviderSignInErrors, ProviderSignUpErrors } from '#api/types'

export type ApiErrors = ProviderSignUpErrors | ApiErrorsGeneral | ProviderSignInErrors | FollowErrors
export type ApiErrors = ProviderSignUpErrors | ApiErrorsGeneral | ProviderSignInErrors | FollowErrors | ApiSignUpErrors

export enum ApiErrorsGeneral {
default = 'Something went wrong',
Expand Down