done connecting coze and youwei
This commit is contained in:
215
backend/auth-api.js
Normal file
215
backend/auth-api.js
Normal file
@@ -0,0 +1,215 @@
|
||||
import dotenv from 'dotenv';
|
||||
import express from 'express';
|
||||
import cors from 'cors';
|
||||
import mysql from 'mysql2/promise';
|
||||
import bcrypt from 'bcryptjs';
|
||||
import argon2 from 'argon2';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
const app = express();
|
||||
const port = 3010;
|
||||
|
||||
app.use(
|
||||
cors({
|
||||
origin(origin, callback) {
|
||||
if (!origin) {
|
||||
callback(null, true);
|
||||
return;
|
||||
}
|
||||
callback(null, true);
|
||||
}
|
||||
})
|
||||
);
|
||||
app.use(express.json());
|
||||
|
||||
const pool = mysql.createPool({
|
||||
host: process.env.DB_HOST || 'nw.sgcode.cn',
|
||||
port: Number(process.env.DB_PORT || 21434),
|
||||
user: process.env.DB_USER || 'root',
|
||||
password: process.env.DB_PASSWORD || 'root',
|
||||
database: process.env.DB_NAME || 'opencoze',
|
||||
waitForConnections: true,
|
||||
connectionLimit: 10
|
||||
});
|
||||
|
||||
async function ensureUsersTable() {
|
||||
await pool.query(`
|
||||
CREATE TABLE IF NOT EXISTS \`user\` (
|
||||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||||
name VARCHAR(128) NOT NULL DEFAULT '',
|
||||
unique_name VARCHAR(128) NOT NULL UNIQUE,
|
||||
email VARCHAR(128) NOT NULL UNIQUE,
|
||||
password VARCHAR(128) NOT NULL DEFAULT '',
|
||||
description VARCHAR(512) NOT NULL DEFAULT '',
|
||||
icon_uri VARCHAR(512) NOT NULL DEFAULT '',
|
||||
user_verified TINYINT(1) NOT NULL DEFAULT 0,
|
||||
locale VARCHAR(128) NOT NULL DEFAULT '',
|
||||
session_key VARCHAR(256) NOT NULL DEFAULT '',
|
||||
created_at BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
updated_at BIGINT UNSIGNED NOT NULL DEFAULT 0,
|
||||
deleted_at BIGINT UNSIGNED NULL DEFAULT NULL,
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
`);
|
||||
}
|
||||
|
||||
app.get('/api/health', async (_req, res) => {
|
||||
try {
|
||||
await pool.query('SELECT 1');
|
||||
res.json({ ok: true });
|
||||
} catch (error) {
|
||||
console.error('Health check failed:', error);
|
||||
res.status(500).json({ ok: false, message: 'Database connection failed' });
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/register', async (req, res) => {
|
||||
let connection;
|
||||
try {
|
||||
const { email, password, confirmPassword } = req.body ?? {};
|
||||
|
||||
if (!email || !password || !confirmPassword) {
|
||||
res.status(400).json({ message: 'Email, password, and confirm password are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (password !== confirmPassword) {
|
||||
res.status(400).json({ message: 'Passwords do not match' });
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedEmail = String(email).trim().toLowerCase();
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(trimmedEmail)) {
|
||||
res.status(400).json({ message: 'Please enter a valid email address' });
|
||||
return;
|
||||
}
|
||||
|
||||
if (String(password).length < 6) {
|
||||
res.status(400).json({ message: 'Password must be at least 6 characters' });
|
||||
return;
|
||||
}
|
||||
|
||||
const [existingRows] = await pool.query('SELECT id FROM `user` WHERE email = ? LIMIT 1', [trimmedEmail]);
|
||||
if (existingRows.length > 0) {
|
||||
res.status(409).json({ message: 'Email is already registered' });
|
||||
return;
|
||||
}
|
||||
|
||||
// New users are stored with Argon2id hashes.
|
||||
const passwordHash = await argon2.hash(String(password), { type: argon2.argon2id });
|
||||
const uniqueName = trimmedEmail;
|
||||
const displayName = trimmedEmail.split('@')[0];
|
||||
const now = Date.now();
|
||||
|
||||
const defaultIconUri = 'default_icon/user_default_icon.png';
|
||||
const personalSpaceName = 'Personal Space';
|
||||
const personalSpaceDescription = 'This is your personal space';
|
||||
|
||||
connection = await pool.getConnection();
|
||||
await connection.beginTransaction();
|
||||
|
||||
await connection.query(
|
||||
'INSERT INTO `user` (name, unique_name, email, password, description, icon_uri, user_verified, locale, session_key, created_at, updated_at, deleted_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[displayName, uniqueName, trimmedEmail, passwordHash, '', defaultIconUri, 0, 'zh-CN', '', now, now, null]
|
||||
);
|
||||
|
||||
const [createdUserRows] = await connection.query('SELECT CAST(id AS CHAR) AS id FROM `user` WHERE email = ? LIMIT 1', [
|
||||
trimmedEmail
|
||||
]);
|
||||
const userId = createdUserRows[0]?.id;
|
||||
if (!userId) {
|
||||
throw new Error('Failed to resolve created user ID');
|
||||
}
|
||||
|
||||
await connection.query(
|
||||
'INSERT INTO `space` (owner_id, name, description, icon_uri, creator_id, created_at, updated_at, deleted_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)',
|
||||
[userId, personalSpaceName, personalSpaceDescription, defaultIconUri, userId, now, now, null]
|
||||
);
|
||||
|
||||
const [createdSpaceRows] = await connection.query(
|
||||
'SELECT CAST(id AS CHAR) AS id FROM `space` WHERE owner_id = ? ORDER BY created_at DESC, id DESC LIMIT 1',
|
||||
[userId]
|
||||
);
|
||||
const spaceId = createdSpaceRows[0]?.id;
|
||||
if (!spaceId) {
|
||||
throw new Error('Failed to resolve created space ID');
|
||||
}
|
||||
|
||||
await connection.query(
|
||||
'INSERT INTO `space_user` (space_id, user_id, role_type, created_at, updated_at) VALUES (?, ?, ?, ?, ?)',
|
||||
[spaceId, userId, 1, now, now]
|
||||
);
|
||||
|
||||
await connection.commit();
|
||||
|
||||
res.status(201).json({ message: 'Registered successfully' });
|
||||
} catch (error) {
|
||||
if (connection) {
|
||||
await connection.rollback();
|
||||
}
|
||||
console.error('Register failed:', error);
|
||||
res.status(500).json({ message: 'Register failed' });
|
||||
} finally {
|
||||
if (connection) {
|
||||
connection.release();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.post('/api/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body ?? {};
|
||||
if (!email || !password) {
|
||||
res.status(400).json({ message: 'Email and password are required' });
|
||||
return;
|
||||
}
|
||||
|
||||
const trimmedEmail = String(email).trim().toLowerCase();
|
||||
const [rows] = await pool.query('SELECT id, email, password FROM `user` WHERE email = ? LIMIT 1', [trimmedEmail]);
|
||||
const user = rows[0];
|
||||
|
||||
if (!user) {
|
||||
res.status(401).json({ message: 'Invalid email or password' });
|
||||
return;
|
||||
}
|
||||
|
||||
const rawPassword = String(password);
|
||||
const storedPassword = String(user.password || '');
|
||||
let isPasswordValid = false;
|
||||
|
||||
if (storedPassword.startsWith('$argon2')) {
|
||||
isPasswordValid = await argon2.verify(storedPassword, rawPassword);
|
||||
} else if (storedPassword.startsWith('$2')) {
|
||||
isPasswordValid = await bcrypt.compare(rawPassword, storedPassword);
|
||||
}
|
||||
|
||||
if (!isPasswordValid) {
|
||||
res.status(401).json({ message: 'Invalid email or password' });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
message: 'Login successful',
|
||||
user: {
|
||||
id: user.id,
|
||||
email: user.email
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error);
|
||||
res.status(500).json({ message: 'Login failed' });
|
||||
}
|
||||
});
|
||||
|
||||
ensureUsersTable()
|
||||
.then(() => {
|
||||
app.listen(port, () => {
|
||||
console.log(`Auth API running at http://localhost:${port}`);
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error('Failed to initialize database:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user