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
19 changes: 3 additions & 16 deletions backend/src/routes/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

res.json({
success: true,
message: 'User created successfully. Please check your email to verify your account.'
message: 'User created successfully'
});
} catch (error) {
next(error);
Expand Down Expand Up @@ -141,7 +141,7 @@
return;
}

const { password_hash, ...userData } = user;

Check failure on line 144 in backend/src/routes/auth.ts

View workflow job for this annotation

GitHub Actions / CI - Lint and Build

'password_hash' is assigned a value but never used

res.json({
success: true,
Expand Down Expand Up @@ -208,28 +208,15 @@
success: false,
error: {
code: '400',
message: 'Verification code is required'
}
});
return;
}

const { user } = await supabaseService.verifyOtp(code, 'signup');

if (!user) {
res.status(400).json({
success: false,
error: {
code: '400',
message: 'Invalid or expired verification code'
message: 'Code is required'
}
});
return;
}

res.json({
success: true,
message: 'Email verified successfully'
message: 'Code processed successfully'
});
} catch (error) {
next(error);
Expand Down
2 changes: 1 addition & 1 deletion backend/src/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export class AuthService {
`INSERT INTO users (email, username, password_hash, role, first_name, last_name, supabase_auth_id, email_verified)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
RETURNING id, email, username, role, created_at, updated_at`,
[email.toLowerCase(), username, null, 'user', firstName, lastName, supabaseAuthId, false] // Set email_verified to false initially
[email.toLowerCase(), username, null, 'user', firstName, lastName, supabaseAuthId, true]
);

const user = result.rows[0];
Expand Down
5 changes: 1 addition & 4 deletions backend/src/services/supabase.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
let page = 1;
const perPage = 1000;

while (true) {

Check failure on line 45 in backend/src/services/supabase.service.ts

View workflow job for this annotation

GitHub Actions / CI - Lint and Build

Unexpected constant condition
const { data, error } = await this.client.auth.admin.listUsers({
page,
perPage
Expand Down Expand Up @@ -77,7 +77,7 @@
const { data, error } = await this.client.auth.admin.createUser({
email,
password,
email_confirm: false, // Supabase will send verification email if configured
email_confirm: true,
user_metadata: {
first_name: metadata.first_name || '',
last_name: metadata.last_name || ''
Expand Down Expand Up @@ -140,9 +140,6 @@
if (error.message.includes('Invalid login credentials') || error.message.includes('Invalid')) {
throw new CustomError('Invalid email or password', 401);
}
if (error.message.includes('Email not confirmed') || error.message.includes('not confirmed')) {
throw new CustomError('Please verify your email address', 403);
}

throw new CustomError(`Login failed: ${error.message}`, 401);
}
Expand Down
18 changes: 11 additions & 7 deletions frontend/MusicApp/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,37 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<!-- Allow HTTP connections for local development -->
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>CFBundleURLSchemes</key>
<array>
<string>musiq</string>
</array>
</dict>
</array>
<key>NSAppTransportSecurity</key>
<dict>
<!-- Allow local networking (localhost and LAN) -->
<key>NSAllowsLocalNetworking</key>
<true/>
<!-- Allow arbitrary loads for local development -->
<key>NSAllowsArbitraryLoadsInWebContent</key>
<true/>
<!-- Explicit exception domains -->
<key>NSExceptionDomains</key>
<dict>
<!-- Allow localhost -->
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<!-- Allow your Mac's local IP (current) -->
<key>192.168.1.244</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<!-- Allow your Mac's local IP (old, for compatibility) -->
<key>192.168.86.133</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
Expand Down
18 changes: 8 additions & 10 deletions frontend/MusicApp/Models/AuthToken.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,32 @@ import Foundation
struct AuthToken: Codable {
let accessToken: String
let refreshToken: String
let expiresIn: Int
let expiresIn: Int
let tokenType: String
let emailVerified: Bool?

enum CodingKeys: String, CodingKey {
case accessToken
case refreshToken
case expiresIn
case tokenType
case emailVerified
}
}

struct LoginRequest: Codable {
let username: String
let email: String
let password: String
}

struct SignupRequest: Codable {
let email: String
let username: String
let password: String
let confirmPassword: String
let firstName: String
let lastName: String
}

struct RefreshTokenRequest: Codable {
let refreshToken: String
}

struct OAuthRequest: Codable {
let provider: String
let token: String
let idToken: String?
}
}
8 changes: 8 additions & 0 deletions frontend/MusicApp/Models/User.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ struct User: Identifiable, Codable {
let role: UserRole
let oauthProvider: OAuthProvider?
let oauthId: String?
let firstName: String?
let lastName: String?
let lastLoginAt: Date?
let createdAt: Date
let updatedAt: Date
Expand All @@ -33,6 +35,8 @@ struct User: Identifiable, Codable {
case role
case oauthProvider = "oauth_provider"
case oauthId = "oauth_id"
case firstName = "first_name"
case lastName = "last_name"
case lastLoginAt = "last_login_at"
case createdAt = "created_at"
case updatedAt = "updated_at"
Expand All @@ -48,6 +52,8 @@ struct User: Identifiable, Codable {
role = try container.decode(UserRole.self, forKey: .role)
oauthProvider = try container.decodeIfPresent(OAuthProvider.self, forKey: .oauthProvider)
oauthId = try container.decodeIfPresent(String.self, forKey: .oauthId)
firstName = try container.decodeIfPresent(String.self, forKey: .firstName)
lastName = try container.decodeIfPresent(String.self, forKey: .lastName)

if let lastLoginString = try? container.decode(String.self, forKey: .lastLoginAt) {
let formatter = ISO8601DateFormatter()
Expand Down Expand Up @@ -75,6 +81,8 @@ struct User: Identifiable, Codable {
try container.encode(role, forKey: .role)
try container.encodeIfPresent(oauthProvider, forKey: .oauthProvider)
try container.encodeIfPresent(oauthId, forKey: .oauthId)
try container.encodeIfPresent(firstName, forKey: .firstName)
try container.encodeIfPresent(lastName, forKey: .lastName)

let formatter = ISO8601DateFormatter()
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
Expand Down
38 changes: 1 addition & 37 deletions frontend/MusicApp/MusicAppApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,44 +8,8 @@ struct MusIQApp: App {
WindowGroup {
ContentView()
.environmentObject(appState)
.onOpenURL { url in

handleOAuthCallback(url: url)
}
}
}

private func handleOAuthCallback(url: URL) {

let components = URLComponents(url: url, resolvingAgainstBaseURL: false)

guard let host = url.host,
let queryItems = components?.queryItems,
let code = queryItems.first(where: { $0.name == "code" })?.value else {
return
}

var provider: OAuthProviderType?
if host.contains("google") {
provider = .google
} else if host.contains("apple") {
provider = .apple
}

guard let provider = provider else {
return
}

let idToken = queryItems.first(where: { $0.name == "id_token" })?.value

NotificationCenter.default.post(
name: NSNotification.Name("OAuthCallback"),
object: nil,
userInfo: [
"provider": provider.rawValue,
"code": code,
"idToken": idToken as Any
]
)
}

}
30 changes: 18 additions & 12 deletions frontend/MusicApp/Services/APIService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,7 @@ class APIService {
throw NetworkError.unknown(NSError(domain: "APIService", code: -1))
}

switch httpResponse.statusCode {
case 200...299:
if (200...299).contains(httpResponse.statusCode) {
do {
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
Expand All @@ -64,17 +63,24 @@ class APIService {
print("Decoding Error Details: \(decodingError)")
throw NetworkError.unknown(decodingError)
}
case 401:
throw NetworkError.unauthorized
case 403:
throw NetworkError.forbidden
case 404:
throw NetworkError.notFound
case 500...599:
throw NetworkError.serverError(httpResponse.statusCode)
default:
throw NetworkError.serverError(httpResponse.statusCode)
}

var message: String = "Server error: \(httpResponse.statusCode)"
if !data.isEmpty {
if let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []),
let json = jsonObject as? [String: Any] {
if let errorDict = json["error"] as? [String: Any],
let serverMessage = errorDict["message"] as? String,
!serverMessage.isEmpty {
message = serverMessage
} else if let serverMessage = json["message"] as? String,
!serverMessage.isEmpty {
message = serverMessage
}
}
}

throw NetworkError.unknown(NSError(domain: "APIService", code: httpResponse.statusCode, userInfo: [NSLocalizedDescriptionKey: message]))
} catch let error as NetworkError {
throw error
} catch {
Expand Down
66 changes: 27 additions & 39 deletions frontend/MusicApp/Services/AuthService.swift
Original file line number Diff line number Diff line change
@@ -1,70 +1,64 @@
import Foundation
import Supabase

class AuthService {
private let supabase = SupabaseClient(supabaseURL: URL(string: "https://mehxapfmnzalknthnzpy.supabase.co")!, supabaseKey: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Im1laHhhcGZtbnphbGtudGhuenB5Iiwicm9sZSI6ImFub24iLCJpYXQiOjE3Njg1MzAwMTAsImV4cCI6MjA4NDEwNjAxMH0.AIwgjmaPgoIm87iHu_ugzx0mTc1wD9TekIH3_Z0M7gQ")
private let apiService = APIService.shared
func login(request: LoginRequest) async throws -> AuthToken {

func signup(email: String, password: String, firstName: String, lastName: String, username: String) async throws {
do {
let response: APIResponse<AuthToken> = try await apiService.request(
endpoint: "/auth/login",
let response: APIResponse<EmptyResponse> = try await apiService.request(
endpoint: "/auth/signup",
method: .post,
body: request,
body: SignupRequest(email: email, username: username, password: password, firstName: firstName, lastName: lastName),
requiresAuth: false
)
guard response.success, let data = response.data else {

guard response.success else {
if let error = response.error {
throw NetworkError.unknown(NSError(domain: "AuthService", code: Int(error.code) ?? 401, userInfo: [NSLocalizedDescriptionKey: error.message]))
throw NetworkError.unknown(NSError(domain: "AuthService", code: Int(error.code) ?? 400, userInfo: [NSLocalizedDescriptionKey: error.message]))
}
throw NetworkError.unauthorized
throw NetworkError.unknown(NSError(domain: "AuthService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Signup failed"]))
}

return data
} catch let error as NetworkError {
throw error
} catch {
throw NetworkError.unknown(error)
}
}
func signup(request: SignupRequest) async throws -> AuthToken {

func login(email: String, password: String) async throws -> AuthToken {
do {
let response: APIResponse<AuthToken> = try await apiService.request(
endpoint: "/auth/signup",
endpoint: "/auth/login",
method: .post,
body: request,
body: LoginRequest(email: email, password: password),
requiresAuth: false
)

guard response.success, let data = response.data else {
if let error = response.error {
throw NetworkError.unknown(NSError(domain: "AuthService", code: Int(error.code) ?? 400, userInfo: [NSLocalizedDescriptionKey: error.message]))
throw NetworkError.unknown(NSError(domain: "AuthService", code: Int(error.code) ?? 401, userInfo: [NSLocalizedDescriptionKey: error.message]))
}
throw NetworkError.unknown(NSError(domain: "AuthService", code: -1, userInfo: [NSLocalizedDescriptionKey: "Signup failed"]))
throw NetworkError.unauthorized
}

return data
} catch let error as NetworkError {
throw error
} catch {
throw NetworkError.unknown(error)
}
}

func refreshToken(request: RefreshTokenRequest) async throws -> AuthToken {
let response: APIResponse<AuthToken> = try await apiService.request(
endpoint: "/auth/refresh",
method: .post,
body: request,
requiresAuth: false
)

guard response.success, let data = response.data else {
throw NetworkError.unauthorized

func forgotPassword(email: String) async throws {
do {
try await supabase.auth.resetPasswordForEmail(email)
} catch {
throw NetworkError.unknown(error)
}

return data
}



func getCurrentUser() async throws -> User {
let response: APIResponse<User> = try await apiService.request(
Expand All @@ -80,13 +74,7 @@ class AuthService {
}

func logout() async throws {
_ = try await apiService.request(
endpoint: "/auth/logout",
method: .post,
body: EmptyBody(),
requiresAuth: true
) as APIResponse<EmptyResponse>

try await supabase.auth.signOut()
KeychainHelper.clearAll()
}
}
Expand Down
Loading
Loading