ENH - add CityMarkt password authentication

Authenticate CityMarkt profiles with the linked iFruit account password, including server-side validation and rate limiting. Update the shared auth UI, localized copy, frontend state, browser mocks, and regression coverage for login and registration flows.
This commit is contained in:
smx.pusha
2026-08-18 07:31:55 +02:00
parent 0f39d6ab8a
commit 77c1790f8b
11 changed files with 811 additions and 279 deletions
+65 -2
View File
@@ -9697,6 +9697,53 @@ app.post('/api/:endpoint', (request, response) => {
)
return
}
if (endpoint === 'marketplace:auth') {
const password = String(request.body.password ?? '')
if (password.length < 6 || password.length > 64) {
response.json({
success: false,
error:
request.body.mode === 'register'
? 'invalid_password'
: 'invalid_credentials',
})
return
}
if (request.body.mode === 'login') {
response.json(
marketplaceProfile.exists
? { success: true, data: marketplaceProfile }
: { success: false, error: 'profile_not_found' },
)
return
}
if (request.body.mode !== 'register') {
response.json({ success: false, error: 'invalid_request' })
return
}
if (marketplaceProfile.exists) {
response.json({ success: false, error: 'profile_exists' })
return
}
const avatarMediaId = Number(request.body.avatarMediaId) || 0
const avatar = mockMedia.find(
(item) => item.id === avatarMediaId && item.mediaType === 'photo',
)
if (avatarMediaId > 0 && !avatar) {
response.json({ success: false, error: 'invalid_profile_image' })
return
}
marketplaceProfile = {
...marketplaceProfile,
avatar_media_id: avatarMediaId || null,
avatar_url: avatar?.url ?? null,
bio: '',
display_name: linkedAccount?.email?.split('@')[0] ?? 'CityMarkt',
exists: true,
}
response.json({ success: true, data: marketplaceProfile })
return
}
if (endpoint.startsWith('marketplace:') && !authenticated) {
response.json({ success: false, error: 'not_authenticated' })
return
@@ -9792,7 +9839,7 @@ app.post('/api/:endpoint', (request, response) => {
item_condition: request.body.condition,
price_type: request.body.priceType,
show_phone: request.body.showPhone ? 1 : 0,
phone_number: null,
phone_number: request.body.showPhone ? (mockSim?.number ?? null) : null,
status: 'active',
revision: 1,
created_at: '2026-08-06 11:00:00',
@@ -9831,8 +9878,10 @@ app.post('/api/:endpoint', (request, response) => {
image: selected[0]?.gradient ?? null,
images: selected,
item_condition: request.body.condition,
phone_number: request.body.showPhone ? (mockSim?.number ?? null) : null,
price_type: request.body.priceType,
revision: item.revision + 1,
show_phone: request.body.showPhone ? 1 : 0,
})
response.json({ success: true, data: { revision: item.revision } })
return
@@ -9849,7 +9898,21 @@ app.post('/api/:endpoint', (request, response) => {
const item = marketplaceListings.find(
(listing) => listing.id === request.body.id,
)
if (item) item.status = request.body.status
if (!item) {
response.json({ success: false, error: 'listing_not_found' })
return
}
const validTransitions = {
active: ['reserved', 'expired', 'sold'],
removed: ['active', 'reserved', 'expired'],
reserved: ['active'],
sold: ['active', 'reserved'],
}
if (!validTransitions[request.body.status]?.includes(item.status)) {
response.json({ success: false, error: 'invalid_status' })
return
}
item.status = request.body.status
response.json({ success: true })
return
}