@ -0,0 +1,172 @@
|
||||
package mathquiz.backend;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Properties;
|
||||
import java.util.Random;
|
||||
|
||||
/**
|
||||
* Email Service Class - Handles email verification code sending
|
||||
*/
|
||||
public class EmailService {
|
||||
|
||||
// Email configuration - loaded from properties file
|
||||
private String smtpHost;
|
||||
private String smtpPort;
|
||||
private String emailUsername;
|
||||
private String emailPassword;
|
||||
private String fromEmail;
|
||||
|
||||
public EmailService() {
|
||||
loadEmailConfig();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load email configuration from properties file
|
||||
*/
|
||||
private void loadEmailConfig() {
|
||||
Properties config = new Properties();
|
||||
try {
|
||||
config.load(new FileInputStream("email_config.properties"));
|
||||
smtpHost = config.getProperty("smtp.host", "smtp.gmail.com");
|
||||
smtpPort = config.getProperty("smtp.port", "587");
|
||||
emailUsername = config.getProperty("email.username", "your-email@gmail.com");
|
||||
emailPassword = config.getProperty("email.password", "your-app-password");
|
||||
fromEmail = config.getProperty("email.from", "your-email@gmail.com");
|
||||
} catch (IOException e) {
|
||||
System.err.println("Warning: Could not load email configuration, using default settings");
|
||||
// Use default settings
|
||||
smtpHost = "smtp.gmail.com";
|
||||
smtpPort = "587";
|
||||
emailUsername = "your-email@gmail.com";
|
||||
emailPassword = "your-app-password";
|
||||
fromEmail = "your-email@gmail.com";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send verification code to email
|
||||
*/
|
||||
public String sendVerificationCode(String email) throws Exception {
|
||||
// Generate 6-digit numeric verification code
|
||||
String verificationCode = generateVerificationCode();
|
||||
|
||||
// Send real email
|
||||
sendRealEmail(email, verificationCode);
|
||||
|
||||
// Also save to file for backup verification
|
||||
saveVerificationCodeToFile(email, verificationCode);
|
||||
|
||||
return verificationCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate verification code
|
||||
*/
|
||||
private String generateVerificationCode() {
|
||||
Random random = new Random();
|
||||
StringBuilder code = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
code.append(random.nextInt(10));
|
||||
}
|
||||
|
||||
return code.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send real email using SMTP protocol
|
||||
*/
|
||||
private void sendRealEmail(String email, String verificationCode) throws Exception {
|
||||
try {
|
||||
// Create email content
|
||||
String subject = "Math Quiz System - Registration Verification Code";
|
||||
String content = createEmailContent(verificationCode);
|
||||
|
||||
// Use SimpleEmailSender
|
||||
SimpleEmailSender sender = new SimpleEmailSender(
|
||||
smtpHost,
|
||||
Integer.parseInt(smtpPort),
|
||||
emailUsername,
|
||||
emailPassword,
|
||||
fromEmail
|
||||
);
|
||||
|
||||
sender.sendEmail(email, subject, content);
|
||||
|
||||
System.out.println("Verification code sent successfully to: " + email);
|
||||
System.out.println("Verification code: " + verificationCode);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to send email: " + e.getMessage());
|
||||
// Fall back to console output
|
||||
System.out.println("=== EMAIL VERIFICATION CODE ===");
|
||||
System.out.println("To: " + email);
|
||||
System.out.println("Subject: Math Quiz System - Registration Verification Code");
|
||||
System.out.println("Verification Code: " + verificationCode);
|
||||
System.out.println("Please check your email or use the code above.");
|
||||
System.out.println("================================");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create email content with HTML formatting
|
||||
*/
|
||||
private String createEmailContent(String verificationCode) {
|
||||
return "<html>" +
|
||||
"<head><meta charset='UTF-8'></head>" +
|
||||
"<body style='font-family: Arial, sans-serif; line-height: 1.6; color: #333;'>" +
|
||||
"<div style='max-width: 600px; margin: 0 auto; padding: 20px;'>" +
|
||||
"<h2 style='color: #0066cc; text-align: center;'>Math Quiz System</h2>" +
|
||||
"<h3 style='color: #333;'>Registration Verification</h3>" +
|
||||
"<p>Hello,</p>" +
|
||||
"<p>Thank you for registering with Math Quiz System. Please use the following verification code to complete your registration:</p>" +
|
||||
"<div style='background-color: #f0f8ff; border: 2px solid #0066cc; border-radius: 8px; padding: 20px; text-align: center; margin: 20px 0;'>" +
|
||||
"<h1 style='color: #0066cc; font-size: 32px; margin: 0; letter-spacing: 5px;'>" + verificationCode + "</h1>" +
|
||||
"</div>" +
|
||||
"<p><strong>Important:</strong></p>" +
|
||||
"<ul>" +
|
||||
"<li>This verification code will expire in 5 minutes</li>" +
|
||||
"<li>Please enter this code exactly as shown</li>" +
|
||||
"<li>If you did not request this code, please ignore this email</li>" +
|
||||
"</ul>" +
|
||||
"<p>Best regards,<br>Math Quiz System Team</p>" +
|
||||
"<hr style='border: none; border-top: 1px solid #eee; margin: 20px 0;'>" +
|
||||
"<p style='font-size: 12px; color: #666; text-align: center;'>This is an automated message, please do not reply.</p>" +
|
||||
"</div>" +
|
||||
"</body>" +
|
||||
"</html>";
|
||||
}
|
||||
|
||||
/**
|
||||
* Save verification code to file (for demonstration only)
|
||||
*/
|
||||
private void saveVerificationCodeToFile(String email, String verificationCode) {
|
||||
try {
|
||||
java.nio.file.Files.createDirectories(java.nio.file.Paths.get("data"));
|
||||
java.nio.file.Files.write(
|
||||
java.nio.file.Paths.get("data/verification_codes.txt"),
|
||||
(email + ":" + verificationCode + "\n").getBytes(),
|
||||
java.nio.file.StandardOpenOption.CREATE,
|
||||
java.nio.file.StandardOpenOption.APPEND
|
||||
);
|
||||
} catch (Exception e) {
|
||||
// Ignore file save errors
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate email format
|
||||
*/
|
||||
public boolean isValidEmail(String email) {
|
||||
if (email == null || email.trim().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String trimmedEmail = email.trim();
|
||||
return trimmedEmail.contains("@") &&
|
||||
trimmedEmail.contains(".") &&
|
||||
trimmedEmail.length() > 5;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package mathquiz.backend;
|
||||
|
||||
/**
|
||||
* Question Class - Represents a multiple choice question
|
||||
*/
|
||||
public class Question {
|
||||
private final String question;
|
||||
private final String optionA;
|
||||
private final String optionB;
|
||||
private final String optionC;
|
||||
private final String optionD;
|
||||
private final String correctAnswer;
|
||||
|
||||
public Question(String question, String optionA, String optionB, String optionC, String optionD, String correctAnswer) {
|
||||
this.question = question;
|
||||
this.optionA = optionA;
|
||||
this.optionB = optionB;
|
||||
this.optionC = optionC;
|
||||
this.optionD = optionD;
|
||||
this.correctAnswer = correctAnswer;
|
||||
}
|
||||
|
||||
public String getQuestion() {
|
||||
return question;
|
||||
}
|
||||
|
||||
public String getOptionA() {
|
||||
return optionA;
|
||||
}
|
||||
|
||||
public String getOptionB() {
|
||||
return optionB;
|
||||
}
|
||||
|
||||
public String getOptionC() {
|
||||
return optionC;
|
||||
}
|
||||
|
||||
public String getOptionD() {
|
||||
return optionD;
|
||||
}
|
||||
|
||||
public String getCorrectAnswer() {
|
||||
return correctAnswer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Question{" +
|
||||
"question='" + question + '\'' +
|
||||
", optionA='" + optionA + '\'' +
|
||||
", optionB='" + optionB + '\'' +
|
||||
", optionC='" + optionC + '\'' +
|
||||
", optionD='" + optionD + '\'' +
|
||||
", correctAnswer='" + correctAnswer + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,184 @@
|
||||
package mathquiz.backend;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.*;
|
||||
import javax.net.ssl.*;
|
||||
|
||||
/**
|
||||
* Simple Email Sender using SMTP protocol
|
||||
* This class provides a basic SMTP client implementation
|
||||
*/
|
||||
public class SimpleEmailSender {
|
||||
|
||||
private String smtpHost;
|
||||
private int smtpPort;
|
||||
private String username;
|
||||
private String password;
|
||||
private String fromEmail;
|
||||
|
||||
public SimpleEmailSender(String smtpHost, int smtpPort, String username, String password, String fromEmail) {
|
||||
this.smtpHost = smtpHost;
|
||||
this.smtpPort = smtpPort;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
this.fromEmail = fromEmail;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send email using SMTP protocol with SSL/TLS support
|
||||
*/
|
||||
public void sendEmail(String toEmail, String subject, String content) throws Exception {
|
||||
Socket socket = null;
|
||||
BufferedReader reader = null;
|
||||
PrintWriter writer = null;
|
||||
|
||||
try {
|
||||
System.out.println("Connecting to SMTP server: " + smtpHost + ":" + smtpPort);
|
||||
|
||||
// Connect to SMTP server
|
||||
socket = new Socket(smtpHost, smtpPort);
|
||||
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
|
||||
writer = new PrintWriter(socket.getOutputStream(), true);
|
||||
|
||||
// Read server greeting
|
||||
String response = reader.readLine();
|
||||
System.out.println("Server greeting: " + response);
|
||||
if (!response.startsWith("220")) {
|
||||
throw new Exception("SMTP server error: " + response);
|
||||
}
|
||||
|
||||
// Send EHLO command
|
||||
String hostname = InetAddress.getLocalHost().getHostName();
|
||||
if (hostname == null || hostname.isEmpty()) {
|
||||
hostname = "localhost";
|
||||
}
|
||||
writer.println("EHLO " + hostname);
|
||||
response = reader.readLine();
|
||||
System.out.println("EHLO response: " + response);
|
||||
if (!response.startsWith("250")) {
|
||||
throw new Exception("EHLO failed: " + response);
|
||||
}
|
||||
|
||||
// Read additional EHLO responses
|
||||
while (response.startsWith("250-")) {
|
||||
response = reader.readLine();
|
||||
System.out.println("EHLO continuation: " + response);
|
||||
}
|
||||
|
||||
// Start TLS if supported
|
||||
writer.println("STARTTLS");
|
||||
response = reader.readLine();
|
||||
System.out.println("STARTTLS response: " + response);
|
||||
if (response.startsWith("220")) {
|
||||
// Upgrade to SSL socket
|
||||
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
|
||||
SSLSocket sslSocket = (SSLSocket) factory.createSocket(socket, smtpHost, smtpPort, true);
|
||||
sslSocket.startHandshake();
|
||||
|
||||
// Update streams to use SSL socket
|
||||
reader = new BufferedReader(new InputStreamReader(sslSocket.getInputStream()));
|
||||
writer = new PrintWriter(sslSocket.getOutputStream(), true);
|
||||
|
||||
// Send EHLO again after TLS
|
||||
writer.println("EHLO " + hostname);
|
||||
response = reader.readLine();
|
||||
System.out.println("EHLO after TLS: " + response);
|
||||
if (!response.startsWith("250")) {
|
||||
throw new Exception("EHLO after TLS failed: " + response);
|
||||
}
|
||||
|
||||
// Read additional EHLO responses
|
||||
while (response.startsWith("250-")) {
|
||||
response = reader.readLine();
|
||||
System.out.println("EHLO continuation after TLS: " + response);
|
||||
}
|
||||
}
|
||||
|
||||
// Authenticate using AUTH LOGIN
|
||||
writer.println("AUTH LOGIN");
|
||||
response = reader.readLine();
|
||||
System.out.println("AUTH LOGIN response: " + response);
|
||||
if (!response.startsWith("334")) {
|
||||
throw new Exception("AUTH LOGIN failed: " + response);
|
||||
}
|
||||
|
||||
// Send username (base64 encoded)
|
||||
String encodedUsername = java.util.Base64.getEncoder().encodeToString(username.getBytes());
|
||||
writer.println(encodedUsername);
|
||||
response = reader.readLine();
|
||||
System.out.println("Username auth response: " + response);
|
||||
if (!response.startsWith("334")) {
|
||||
throw new Exception("Username authentication failed: " + response);
|
||||
}
|
||||
|
||||
// Send password (base64 encoded)
|
||||
String encodedPassword = java.util.Base64.getEncoder().encodeToString(password.getBytes());
|
||||
writer.println(encodedPassword);
|
||||
response = reader.readLine();
|
||||
System.out.println("Password auth response: " + response);
|
||||
if (!response.startsWith("235")) {
|
||||
throw new Exception("Password authentication failed: " + response);
|
||||
}
|
||||
|
||||
// Send MAIL FROM
|
||||
writer.println("MAIL FROM:<" + fromEmail + ">");
|
||||
response = reader.readLine();
|
||||
System.out.println("MAIL FROM response: " + response);
|
||||
if (!response.startsWith("250")) {
|
||||
throw new Exception("MAIL FROM failed: " + response);
|
||||
}
|
||||
|
||||
// Send RCPT TO
|
||||
writer.println("RCPT TO:<" + toEmail + ">");
|
||||
response = reader.readLine();
|
||||
System.out.println("RCPT TO response: " + response);
|
||||
if (!response.startsWith("250")) {
|
||||
throw new Exception("RCPT TO failed: " + response);
|
||||
}
|
||||
|
||||
// Send DATA
|
||||
writer.println("DATA");
|
||||
response = reader.readLine();
|
||||
System.out.println("DATA response: " + response);
|
||||
if (!response.startsWith("354")) {
|
||||
throw new Exception("DATA failed: " + response);
|
||||
}
|
||||
|
||||
// Send email headers and content
|
||||
writer.println("From: " + fromEmail);
|
||||
writer.println("To: " + toEmail);
|
||||
writer.println("Subject: " + subject);
|
||||
writer.println("MIME-Version: 1.0");
|
||||
writer.println("Content-Type: text/html; charset=UTF-8");
|
||||
writer.println();
|
||||
writer.println(content);
|
||||
writer.println(".");
|
||||
|
||||
response = reader.readLine();
|
||||
System.out.println("Email send response: " + response);
|
||||
if (!response.startsWith("250")) {
|
||||
throw new Exception("Email sending failed: " + response);
|
||||
}
|
||||
|
||||
// Quit
|
||||
writer.println("QUIT");
|
||||
response = reader.readLine();
|
||||
System.out.println("QUIT response: " + response);
|
||||
|
||||
System.out.println("Email sent successfully to: " + toEmail);
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("Failed to send email: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
if (writer != null) writer.close();
|
||||
if (reader != null) reader.close();
|
||||
if (socket != null) socket.close();
|
||||
} catch (IOException e) {
|
||||
// Ignore cleanup errors
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,209 @@
|
||||
package mathquiz.backend;
|
||||
|
||||
import java.io.*;
|
||||
import java.nio.file.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* User Service Class - Handles user registration, login, password management
|
||||
*/
|
||||
public class UserService {
|
||||
private static final String USERS_FILE = "data/users.txt";
|
||||
private static final String PASSWORDS_FILE = "data/passwords.txt";
|
||||
|
||||
private Map<String, String> userPasswords;
|
||||
private Map<String, String> userEmails;
|
||||
|
||||
public UserService() {
|
||||
loadUserData();
|
||||
}
|
||||
|
||||
/**
|
||||
* User registration
|
||||
*/
|
||||
public void registerUser(String email, String username) throws IOException {
|
||||
if (userEmails.containsValue(email)) {
|
||||
throw new IllegalArgumentException("This email has already been registered");
|
||||
}
|
||||
|
||||
userEmails.put(username, email);
|
||||
|
||||
// Save user data
|
||||
saveUserData();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set password
|
||||
*/
|
||||
public void setPassword(String email, String password) throws IOException {
|
||||
if (!isValidPassword(password)) {
|
||||
throw new IllegalArgumentException("Password does not meet requirements");
|
||||
}
|
||||
|
||||
String username = getUsernameByEmail(email);
|
||||
if (username == null) {
|
||||
throw new IllegalArgumentException("User does not exist");
|
||||
}
|
||||
|
||||
userPasswords.put(username, password);
|
||||
savePasswordData();
|
||||
}
|
||||
|
||||
/**
|
||||
* User login
|
||||
*/
|
||||
public boolean login(String email, String password) {
|
||||
String username = getUsernameByEmail(email);
|
||||
if (username == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String storedPassword = userPasswords.get(username);
|
||||
if (storedPassword == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Objects.equals(storedPassword, password)) {
|
||||
// Set current user (use email for session)
|
||||
mathquiz.gui.QuizSession.getInstance().setCurrentUser(email);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change password
|
||||
*/
|
||||
public boolean changePassword(String username, String oldPassword, String newPassword) throws IOException {
|
||||
if (!isValidPassword(newPassword)) {
|
||||
throw new IllegalArgumentException("New password does not meet requirements");
|
||||
}
|
||||
|
||||
String storedPassword = userPasswords.get(username);
|
||||
if (!Objects.equals(storedPassword, oldPassword)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
userPasswords.put(username, newPassword);
|
||||
savePasswordData();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate password format
|
||||
*/
|
||||
private boolean isValidPassword(String password) {
|
||||
if (password.length() < 6 || password.length() > 10) {
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean hasUpperCase = false;
|
||||
boolean hasLowerCase = false;
|
||||
boolean hasDigit = false;
|
||||
|
||||
for (char c : password.toCharArray()) {
|
||||
if (Character.isUpperCase(c)) {
|
||||
hasUpperCase = true;
|
||||
} else if (Character.isLowerCase(c)) {
|
||||
hasLowerCase = true;
|
||||
} else if (Character.isDigit(c)) {
|
||||
hasDigit = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hasUpperCase && hasLowerCase && hasDigit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get username by email
|
||||
*/
|
||||
private String getUsernameByEmail(String email) {
|
||||
for (Map.Entry<String, String> entry : userEmails.entrySet()) {
|
||||
if (entry.getValue().equals(email)) {
|
||||
return entry.getKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate username
|
||||
*/
|
||||
private String generateUsername(String email) {
|
||||
String baseName = email.split("@")[0];
|
||||
String username = baseName;
|
||||
int counter = 1;
|
||||
|
||||
while (userEmails.containsKey(username)) {
|
||||
username = baseName + counter;
|
||||
counter++;
|
||||
}
|
||||
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load user data
|
||||
*/
|
||||
private void loadUserData() {
|
||||
userPasswords = new HashMap<>();
|
||||
userEmails = new HashMap<>();
|
||||
|
||||
try {
|
||||
// Create data directory
|
||||
Files.createDirectories(Paths.get("data"));
|
||||
|
||||
// Load user email mapping
|
||||
if (Files.exists(Paths.get(USERS_FILE))) {
|
||||
for (String line : Files.readAllLines(Paths.get(USERS_FILE))) {
|
||||
String[] parts = line.split(",");
|
||||
if (parts.length == 2) {
|
||||
userEmails.put(parts[0], parts[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load user passwords
|
||||
if (Files.exists(Paths.get(PASSWORDS_FILE))) {
|
||||
for (String line : Files.readAllLines(Paths.get(PASSWORDS_FILE))) {
|
||||
String[] parts = line.split(",");
|
||||
if (parts.length == 2) {
|
||||
userPasswords.put(parts[0], parts[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException e) {
|
||||
// If loading fails, use empty data
|
||||
userPasswords = new HashMap<>();
|
||||
userEmails = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save user data
|
||||
*/
|
||||
private void saveUserData() throws IOException {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(USERS_FILE))) {
|
||||
for (Map.Entry<String, String> entry : userEmails.entrySet()) {
|
||||
writer.write(entry.getKey() + "," + entry.getValue());
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save password data
|
||||
*/
|
||||
private void savePasswordData() throws IOException {
|
||||
try (BufferedWriter writer = Files.newBufferedWriter(Paths.get(PASSWORDS_FILE))) {
|
||||
for (Map.Entry<String, String> entry : userPasswords.entrySet()) {
|
||||
writer.write(entry.getKey() + "," + entry.getValue());
|
||||
writer.newLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in new issue