Refactor user creation queries and add save more Github user informations
- Added a new struct GithubUserInfo to hold Github user information - Modified CreateUser function to accept and process Github user information - Renamed various methods for clearer semantics. - Introduced new functions createDBUser, createDBAccount, createDBGitHubProvider, createDBSession to break down user creation steps - Removed RefreshToken and Expires fields from Provider struct and added new fields (Login, AvatarURL, ProfileURL, Email) - Added additional error checking to various database operations - Removed checkAdmin function and its usage in RenderLoginPage - Added functionality to get Github user details in OAuthCallback
- commit
- c872d875d6202da92d35a3a2d7f080fc2b08a7a6
- author
- bnema · · 2023-10-17 08:45 UTC
- parent
- b6a20a5
- signature
- unsigned
3 files · +156 -104
M internal/db/queries/userqueries.go +80 -58
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package queries | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "database/sql" | |
| 4 | 5 | "fmt" | |
| 5 | 6 | "time" | |
| 6 | 7 | ||
| @@ -13,88 +14,95 @@ func generateUUID() string { | |||
| 13 | 14 | return uuid.New().String() | |
| 14 | 15 | } | |
| 15 | 16 | ||
| 16 | - | // CreateUser creates a new user along with the associated account, provider, and session. | |
| 17 | - | func CreateUser(a *app.App, accessToken string, browserInfo string) error { | |
| 17 | + | // UserInfo holds the essential information for a Github user. | |
| 18 | + | type GithubUserInfo struct { | |
| 19 | + | Login string `json:"login"` | |
| 20 | + | AvatarURL string `json:"avatar_url"` | |
| 21 | + | ProfileURL string `json:"html_url"` | |
| 22 | + | Emails []string | |
| 23 | + | } | |
| 18 | 24 | ||
| 19 | - | // !! Additionnal security check !! | |
| 25 | + | // CreateUser creates a new user along with the associated account, provider, and session. | |
| 26 | + | func CreateUser(a *app.App, accessToken string, browserInfo string, userInfo *GithubUserInfo) error { | |
| 20 | 27 | // Check if a user already exists. If so, return an error. | |
| 21 | - | userExists, err := UserExists(a) | |
| 22 | - | if err != nil { | |
| 23 | - | return err | |
| 28 | + | if exists, err := CheckDBUserExists(a); err != nil || exists { | |
| 29 | + | return fmt.Errorf("error checking user or user already exists: %v", err) | |
| 24 | 30 | } | |
| 25 | - | if userExists { | |
| 26 | - | return fmt.Errorf("user already exists") | |
| 27 | - | } | |
| 28 | - | ||
| 29 | - | user := db.User{} | |
| 30 | - | account := db.Account{} | |
| 31 | 31 | ||
| 32 | - | // Step 1: Create a user | |
| 33 | - | newUUID := generateUUID() | |
| 34 | - | user.ID = newUUID | |
| 35 | - | user.Name = "Test User" | |
| 36 | - | user.Email = "admin@gordon.cul" | |
| 37 | - | ||
| 38 | - | _, err = a.DB.Exec("INSERT INTO user (id ,name, email) VALUES (?, ?, ?)", user.ID, user.Name, user.Email) | |
| 32 | + | user, err := createDBUser(a, userInfo) | |
| 39 | 33 | if err != nil { | |
| 40 | 34 | return err | |
| 41 | 35 | } | |
| 42 | - | ||
| 43 | 36 | a.DBTables.User.ID = user.ID | |
| 44 | - | // Step 2: Create an account for the user with the user ID | |
| 45 | - | account.ID, err = createAccount(a) | |
| 37 | + | ||
| 38 | + | account, err := createDBAccount(a) | |
| 46 | 39 | if err != nil { | |
| 47 | 40 | return err | |
| 48 | 41 | } | |
| 49 | - | // Step 3: Create a provider for the user | |
| 50 | - | if err := createProvider(a, accessToken); err != nil { | |
| 51 | - | return err | |
| 52 | - | } | |
| 42 | + | a.DBTables.Account.ID = account.ID | |
| 53 | 43 | ||
| 54 | - | // Step 4: Create a session for the user | |
| 55 | - | if err := createSession(a, browserInfo); err != nil { | |
| 44 | + | err = createDBGitHubProvider(a, accessToken, userInfo) | |
| 45 | + | if err != nil { | |
| 56 | 46 | return err | |
| 57 | 47 | } | |
| 58 | 48 | ||
| 59 | - | return nil | |
| 49 | + | return createDBSession(a, browserInfo) | |
| 60 | 50 | } | |
| 61 | 51 | ||
| 62 | - | func createAccount(a *app.App) (string, error) { | |
| 63 | - | account := a.DBTables.Account | |
| 64 | - | // Generate a new UUID for the account | |
| 65 | - | account.ID = generateUUID() | |
| 52 | + | func createDBUser(a *app.App, userInfo *GithubUserInfo) (*db.User, error) { | |
| 53 | + | user := &db.User{ | |
| 54 | + | ID: generateUUID(), | |
| 55 | + | Name: userInfo.Login, | |
| 56 | + | Email: userInfo.Emails[0], | |
| 57 | + | } | |
| 66 | 58 | ||
| 67 | - | // Insert the account into the database | |
| 68 | - | _, err := a.DB.Exec("INSERT INTO account (id, user_id) VALUES (?, ?)", account.ID, a.DBTables.User.ID) | |
| 69 | - | if err != nil { | |
| 70 | - | return "", err | |
| 59 | + | _, err := a.DB.Exec( | |
| 60 | + | "INSERT INTO user (id, name, email) VALUES (?, ?, ?)", | |
| 61 | + | user.ID, user.Name, user.Email, | |
| 62 | + | ) | |
| 63 | + | return user, err | |
| 64 | + | } | |
| 65 | + | ||
| 66 | + | func createDBAccount(a *app.App) (*db.Account, error) { | |
| 67 | + | account := &db.Account{ | |
| 68 | + | ID: generateUUID(), | |
| 71 | 69 | } | |
| 72 | - | a.DBTables.Account.ID = account.ID | |
| 73 | - | return account.ID, nil | |
| 70 | + | ||
| 71 | + | _, err := a.DB.Exec( | |
| 72 | + | "INSERT INTO account (id, user_id) VALUES (?, ?)", | |
| 73 | + | account.ID, a.DBTables.User.ID, | |
| 74 | + | ) | |
| 75 | + | return account, err | |
| 74 | 76 | } | |
| 75 | 77 | ||
| 76 | - | func createProvider(a *app.App, accessToken string) error { | |
| 77 | - | provider := a.DBTables.Provider | |
| 78 | - | provider.ID = generateUUID() | |
| 78 | + | func createDBGitHubProvider(a *app.App, accessToken string, userInfo *GithubUserInfo) error { | |
| 79 | + | provider := &db.Provider{ | |
| 80 | + | ID: generateUUID(), | |
| 81 | + | AccessToken: accessToken, | |
| 82 | + | Email: userInfo.Emails[0], | |
| 83 | + | Login: userInfo.Login, | |
| 84 | + | AvatarURL: userInfo.AvatarURL, | |
| 85 | + | ProfileURL: userInfo.ProfileURL, | |
| 86 | + | } | |
| 79 | 87 | ||
| 80 | - | queryInsertProvider := "INSERT INTO provider (id, account_id, name, access_token, refresh_token, expires) VALUES (?, ?, ?, ?, ?, ?)" | |
| 81 | - | _, err := a.DB.Exec(queryInsertProvider, provider.ID, a.DBTables.Account.ID, "GitHub", accessToken, "refreshToken", time.Now().Add(time.Hour*24).Format(time.RFC3339)) | |
| 88 | + | query := "INSERT INTO provider (id, account_id, name, access_token, login, avatar_url, profile_url, email) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" | |
| 89 | + | _, err := a.DB.Exec(query, provider.ID, a.DBTables.Account.ID, "GitHub", provider.AccessToken, provider.Login, provider.AvatarURL, provider.ProfileURL, provider.Email) | |
| 82 | 90 | return err | |
| 83 | 91 | } | |
| 84 | 92 | ||
| 85 | 93 | // createSession creates a session for the user. | |
| 86 | - | func createSession(a *app.App, browserInfo string) error { | |
| 87 | - | sessions := a.DBTables.Sessions | |
| 88 | - | sessions.ID = generateUUID() | |
| 89 | - | ||
| 90 | - | queryInsertSession := "INSERT INTO sessions (id, account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?, ?)" | |
| 91 | - | _, err := a.DB.Exec(queryInsertSession, sessions.ID, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true) | |
| 94 | + | func createDBSession(a *app.App, browserInfo string) error { | |
| 95 | + | sessions := &db.Sessions{ | |
| 96 | + | ID: generateUUID(), | |
| 97 | + | } | |
| 92 | 98 | ||
| 99 | + | query := "INSERT INTO sessions (id, account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?, ?)" | |
| 100 | + | _, err := a.DB.Exec(query, sessions.ID, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true) | |
| 93 | 101 | a.DBTables.Sessions.ID = sessions.ID | |
| 94 | 102 | return err | |
| 95 | 103 | } | |
| 96 | 104 | ||
| 97 | - | func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) error { | |
| 105 | + | func CreateOrUpdateDBSession(a *app.App, accessToken string, browserInfo string) error { | |
| 98 | 106 | existingSessionID := a.DBTables.Sessions.ID | |
| 99 | 107 | ||
| 100 | 108 | queryFindExistingSession := "SELECT id FROM sessions WHERE account_id = ? AND browser_info = ?" | |
| @@ -124,19 +132,33 @@ func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) e | |||
| 124 | 132 | return nil | |
| 125 | 133 | } | |
| 126 | 134 | ||
| 127 | - | func UserExists(a *app.App) (bool, error) { | |
| 128 | - | var count int | |
| 129 | - | err := a.DB.QueryRow("SELECT COUNT(*) FROM user").Scan(&count) | |
| 135 | + | func CheckDBAccountAccessToken(a *app.App, accessToken string) (bool, error) { | |
| 136 | + | var existingAccessToken string | |
| 137 | + | err := a.DB.QueryRow("SELECT access_token FROM provider WHERE name = ?", "GitHub").Scan(&existingAccessToken) | |
| 130 | 138 | if err != nil { | |
| 131 | 139 | return false, err | |
| 132 | 140 | } | |
| 133 | - | if count > 0 { | |
| 141 | + | ||
| 142 | + | if existingAccessToken == accessToken { | |
| 134 | 143 | return true, nil | |
| 135 | 144 | } | |
| 145 | + | ||
| 136 | 146 | return false, nil | |
| 137 | 147 | } | |
| 138 | 148 | ||
| 139 | - | func UpdateAccessToken(a *app.App, accessToken string) error { | |
| 140 | - | _, err := a.DB.Exec("UPDATE provider SET access_token = ? WHERE name = ?", accessToken, "GitHub") | |
| 141 | - | return err | |
| 149 | + | func CheckDBUserExists(a *app.App) (bool, error) { | |
| 150 | + | var userID string | |
| 151 | + | err := a.DB.QueryRow("SELECT id FROM user").Scan(&userID) | |
| 152 | + | if err != nil { | |
| 153 | + | if err == sql.ErrNoRows { | |
| 154 | + | return false, nil | |
| 155 | + | } | |
| 156 | + | return false, err | |
| 157 | + | } | |
| 158 | + | ||
| 159 | + | if userID != "" { | |
| 160 | + | return true, nil | |
| 161 | + | } | |
| 162 | + | ||
| 163 | + | return false, nil | |
| 142 | 164 | } | |
| @@ -1,6 +1,7 @@ | |||||
| 1 | package queries | 1 | package queries | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "database/sql" | |||
| 4 | "fmt" | 5 | "fmt" | ||
| 5 | "time" | 6 | "time" | ||
| 6 | 7 | ||||
| @@ -13,88 +14,95 @@ func generateUUID() string { | |||||
| 13 | return uuid.New().String() | 14 | return uuid.New().String() | ||
| 14 | } | 15 | } | ||
| 15 | 16 | ||||
| 16 | - | // CreateUser creates a new user along with the associated account, provider, and session. | 17 | + | // UserInfo holds the essential information for a Github user. |
| 17 | - | func CreateUser(a *app.App, accessToken string, browserInfo string) error { | 18 | + | type GithubUserInfo struct { |
| 19 | + | Login string `json:"login"` | |||
| 20 | + | AvatarURL string `json:"avatar_url"` | |||
| 21 | + | ProfileURL string `json:"html_url"` | |||
| 22 | + | Emails []string | |||
| 23 | + | } | |||
| 18 | 24 | ||||
| 19 | - | // !! Additionnal security check !! | 25 | + | // CreateUser creates a new user along with the associated account, provider, and session. |
| 26 | + | func CreateUser(a *app.App, accessToken string, browserInfo string, userInfo *GithubUserInfo) error { | |||
| 20 | // Check if a user already exists. If so, return an error. | 27 | // Check if a user already exists. If so, return an error. | ||
| 21 | - | userExists, err := UserExists(a) | 28 | + | if exists, err := CheckDBUserExists(a); err != nil || exists { |
| 22 | - | if err != nil { | 29 | + | return fmt.Errorf("error checking user or user already exists: %v", err) |
| 23 | - | return err | |||
| 24 | } | 30 | } | ||
| 25 | - | if userExists { | |||
| 26 | - | return fmt.Errorf("user already exists") | |||
| 27 | - | } | |||
| 28 | - | ||||
| 29 | - | user := db.User{} | |||
| 30 | - | account := db.Account{} | |||
| 31 | 31 | ||||
| 32 | - | // Step 1: Create a user | 32 | + | user, err := createDBUser(a, userInfo) |
| 33 | - | newUUID := generateUUID() | |||
| 34 | - | user.ID = newUUID | |||
| 35 | - | user.Name = "Test User" | |||
| 36 | - | user.Email = "admin@gordon.cul" | |||
| 37 | - | ||||
| 38 | - | _, err = a.DB.Exec("INSERT INTO user (id ,name, email) VALUES (?, ?, ?)", user.ID, user.Name, user.Email) | |||
| 39 | if err != nil { | 33 | if err != nil { | ||
| 40 | return err | 34 | return err | ||
| 41 | } | 35 | } | ||
| 42 | - | ||||
| 43 | a.DBTables.User.ID = user.ID | 36 | a.DBTables.User.ID = user.ID | ||
| 44 | - | // Step 2: Create an account for the user with the user ID | 37 | + | |
| 45 | - | account.ID, err = createAccount(a) | 38 | + | account, err := createDBAccount(a) |
| 46 | if err != nil { | 39 | if err != nil { | ||
| 47 | return err | 40 | return err | ||
| 48 | } | 41 | } | ||
| 49 | - | // Step 3: Create a provider for the user | 42 | + | a.DBTables.Account.ID = account.ID |
| 50 | - | if err := createProvider(a, accessToken); err != nil { | |||
| 51 | - | return err | |||
| 52 | - | } | |||
| 53 | 43 | ||||
| 54 | - | // Step 4: Create a session for the user | 44 | + | err = createDBGitHubProvider(a, accessToken, userInfo) |
| 55 | - | if err := createSession(a, browserInfo); err != nil { | 45 | + | if err != nil { |
| 56 | return err | 46 | return err | ||
| 57 | } | 47 | } | ||
| 58 | 48 | ||||
| 59 | - | return nil | 49 | + | return createDBSession(a, browserInfo) |
| 60 | } | 50 | } | ||
| 61 | 51 | ||||
| 62 | - | func createAccount(a *app.App) (string, error) { | 52 | + | func createDBUser(a *app.App, userInfo *GithubUserInfo) (*db.User, error) { |
| 63 | - | account := a.DBTables.Account | 53 | + | user := &db.User{ |
| 64 | - | // Generate a new UUID for the account | 54 | + | ID: generateUUID(), |
| 65 | - | account.ID = generateUUID() | 55 | + | Name: userInfo.Login, |
| 56 | + | Email: userInfo.Emails[0], | |||
| 57 | + | } | |||
| 66 | 58 | ||||
| 67 | - | // Insert the account into the database | 59 | + | _, err := a.DB.Exec( |
| 68 | - | _, err := a.DB.Exec("INSERT INTO account (id, user_id) VALUES (?, ?)", account.ID, a.DBTables.User.ID) | 60 | + | "INSERT INTO user (id, name, email) VALUES (?, ?, ?)", |
| 69 | - | if err != nil { | 61 | + | user.ID, user.Name, user.Email, |
| 70 | - | return "", err | 62 | + | ) |
| 63 | + | return user, err | |||
| 64 | + | } | |||
| 65 | + | ||||
| 66 | + | func createDBAccount(a *app.App) (*db.Account, error) { | |||
| 67 | + | account := &db.Account{ | |||
| 68 | + | ID: generateUUID(), | |||
| 71 | } | 69 | } | ||
| 72 | - | a.DBTables.Account.ID = account.ID | 70 | + | |
| 73 | - | return account.ID, nil | 71 | + | _, err := a.DB.Exec( |
| 72 | + | "INSERT INTO account (id, user_id) VALUES (?, ?)", | |||
| 73 | + | account.ID, a.DBTables.User.ID, | |||
| 74 | + | ) | |||
| 75 | + | return account, err | |||
| 74 | } | 76 | } | ||
| 75 | 77 | ||||
| 76 | - | func createProvider(a *app.App, accessToken string) error { | 78 | + | func createDBGitHubProvider(a *app.App, accessToken string, userInfo *GithubUserInfo) error { |
| 77 | - | provider := a.DBTables.Provider | 79 | + | provider := &db.Provider{ |
| 78 | - | provider.ID = generateUUID() | 80 | + | ID: generateUUID(), |
| 81 | + | AccessToken: accessToken, | |||
| 82 | + | Email: userInfo.Emails[0], | |||
| 83 | + | Login: userInfo.Login, | |||
| 84 | + | AvatarURL: userInfo.AvatarURL, | |||
| 85 | + | ProfileURL: userInfo.ProfileURL, | |||
| 86 | + | } | |||
| 79 | 87 | ||||
| 80 | - | queryInsertProvider := "INSERT INTO provider (id, account_id, name, access_token, refresh_token, expires) VALUES (?, ?, ?, ?, ?, ?)" | 88 | + | query := "INSERT INTO provider (id, account_id, name, access_token, login, avatar_url, profile_url, email) VALUES (?, ?, ?, ?, ?, ?, ?, ?)" |
| 81 | - | _, err := a.DB.Exec(queryInsertProvider, provider.ID, a.DBTables.Account.ID, "GitHub", accessToken, "refreshToken", time.Now().Add(time.Hour*24).Format(time.RFC3339)) | 89 | + | _, err := a.DB.Exec(query, provider.ID, a.DBTables.Account.ID, "GitHub", provider.AccessToken, provider.Login, provider.AvatarURL, provider.ProfileURL, provider.Email) |
| 82 | return err | 90 | return err | ||
| 83 | } | 91 | } | ||
| 84 | 92 | ||||
| 85 | // createSession creates a session for the user. | 93 | // createSession creates a session for the user. | ||
| 86 | - | func createSession(a *app.App, browserInfo string) error { | 94 | + | func createDBSession(a *app.App, browserInfo string) error { |
| 87 | - | sessions := a.DBTables.Sessions | 95 | + | sessions := &db.Sessions{ |
| 88 | - | sessions.ID = generateUUID() | 96 | + | ID: generateUUID(), |
| 89 | - | 97 | + | } | |
| 90 | - | queryInsertSession := "INSERT INTO sessions (id, account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?, ?)" | |||
| 91 | - | _, err := a.DB.Exec(queryInsertSession, sessions.ID, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true) | |||
| 92 | 98 | ||||
| 99 | + | query := "INSERT INTO sessions (id, account_id, browser_info, expires, is_online) VALUES (?, ?, ?, ?, ?)" | |||
| 100 | + | _, err := a.DB.Exec(query, sessions.ID, a.DBTables.Account.ID, browserInfo, time.Now().Add(time.Hour*24).Format(time.RFC3339), true) | |||
| 93 | a.DBTables.Sessions.ID = sessions.ID | 101 | a.DBTables.Sessions.ID = sessions.ID | ||
| 94 | return err | 102 | return err | ||
| 95 | } | 103 | } | ||
| 96 | 104 | ||||
| 97 | - | func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) error { | 105 | + | func CreateOrUpdateDBSession(a *app.App, accessToken string, browserInfo string) error { |
| 98 | existingSessionID := a.DBTables.Sessions.ID | 106 | existingSessionID := a.DBTables.Sessions.ID | ||
| 99 | 107 | ||||
| 100 | queryFindExistingSession := "SELECT id FROM sessions WHERE account_id = ? AND browser_info = ?" | 108 | queryFindExistingSession := "SELECT id FROM sessions WHERE account_id = ? AND browser_info = ?" | ||
| @@ -124,19 +132,33 @@ func CreateOrUpdateSession(a *app.App, accessToken string, browserInfo string) e | |||||
| 124 | return nil | 132 | return nil | ||
| 125 | } | 133 | } | ||
| 126 | 134 | ||||
| 127 | - | func UserExists(a *app.App) (bool, error) { | 135 | + | func CheckDBAccountAccessToken(a *app.App, accessToken string) (bool, error) { |
| 128 | - | var count int | 136 | + | var existingAccessToken string |
| 129 | - | err := a.DB.QueryRow("SELECT COUNT(*) FROM user").Scan(&count) | 137 | + | err := a.DB.QueryRow("SELECT access_token FROM provider WHERE name = ?", "GitHub").Scan(&existingAccessToken) |
| 130 | if err != nil { | 138 | if err != nil { | ||
| 131 | return false, err | 139 | return false, err | ||
| 132 | } | 140 | } | ||
| 133 | - | if count > 0 { | 141 | + | |
| 142 | + | if existingAccessToken == accessToken { | |||
| 134 | return true, nil | 143 | return true, nil | ||
| 135 | } | 144 | } | ||
| 145 | + | ||||
| 136 | return false, nil | 146 | return false, nil | ||
| 137 | } | 147 | } | ||
| 138 | 148 | ||||
| 139 | - | func UpdateAccessToken(a *app.App, accessToken string) error { | 149 | + | func CheckDBUserExists(a *app.App) (bool, error) { |
| 140 | - | _, err := a.DB.Exec("UPDATE provider SET access_token = ? WHERE name = ?", accessToken, "GitHub") | 150 | + | var userID string |
| 141 | - | return err | 151 | + | err := a.DB.QueryRow("SELECT id FROM user").Scan(&userID) |
| 152 | + | if err != nil { | |||
| 153 | + | if err == sql.ErrNoRows { | |||
| 154 | + | return false, nil | |||
| 155 | + | } | |||
| 156 | + | return false, err | |||
| 157 | + | } | |||
| 158 | + | ||||
| 159 | + | if userID != "" { | |||
| 160 | + | return true, nil | |||
| 161 | + | } | |||
| 162 | + | ||||
| 163 | + | return false, nil | |||
| 142 | } | 164 | } | ||
M internal/db/struct.go +8 -6
| @@ -23,10 +23,12 @@ type Sessions struct { | |||
| 23 | 23 | } | |
| 24 | 24 | ||
| 25 | 25 | type Provider struct { | |
| 26 | - | ID string `sql:"id, primary_key"` | |
| 27 | - | AccountID string `sql:"account_id, foreign_key=account.id"` | |
| 28 | - | Name string `sql:"name"` | |
| 29 | - | AccessToken string `sql:"access_token"` | |
| 30 | - | RefreshToken string `sql:"refresh_token"` | |
| 31 | - | Expires string `sql:"expires"` | |
| 26 | + | ID string `sql:"id, primary_key"` | |
| 27 | + | AccountID string `sql:"account_id, foreign_key=account.id"` | |
| 28 | + | Name string `sql:"name"` | |
| 29 | + | AccessToken string `sql:"access_token"` | |
| 30 | + | Login string `sql:"login"` | |
| 31 | + | AvatarURL string `sql:"avatar_url"` | |
| 32 | + | ProfileURL string `sql:"profile_url"` | |
| 33 | + | Email string `sql:"email"` | |
| 32 | 34 | } | |
| @@ -23,10 +23,12 @@ type Sessions struct { | |||||
| 23 | } | 23 | } | ||
| 24 | 24 | ||||
| 25 | type Provider struct { | 25 | type Provider struct { | ||
| 26 | - | ID string `sql:"id, primary_key"` | 26 | + | ID string `sql:"id, primary_key"` |
| 27 | - | AccountID string `sql:"account_id, foreign_key=account.id"` | 27 | + | AccountID string `sql:"account_id, foreign_key=account.id"` |
| 28 | - | Name string `sql:"name"` | 28 | + | Name string `sql:"name"` |
| 29 | - | AccessToken string `sql:"access_token"` | 29 | + | AccessToken string `sql:"access_token"` |
| 30 | - | RefreshToken string `sql:"refresh_token"` | 30 | + | Login string `sql:"login"` |
| 31 | - | Expires string `sql:"expires"` | 31 | + | AvatarURL string `sql:"avatar_url"` |
| 32 | + | ProfileURL string `sql:"profile_url"` | |||
| 33 | + | Email string `sql:"email"` | |||
| 32 | } | 34 | } | ||
M internal/httpserve/handler/oauth.go +68 -40
| @@ -2,7 +2,9 @@ package handler | |||
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | 4 | "encoding/base64" | |
| 5 | + | "encoding/json" | |
| 5 | 6 | "fmt" | |
| 7 | + | "io" | |
| 6 | 8 | "net/http" | |
| 7 | 9 | "os" | |
| 8 | 10 | ||
| @@ -25,35 +27,12 @@ func compareGordonToken(c echo.Context, a *app.App) error { | |||
| 25 | 27 | return nil | |
| 26 | 28 | } | |
| 27 | 29 | ||
| 28 | - | // Check if there is already a user in the db which means that the admin is already setup | |
| 29 | - | func checkAdmin(a *app.App) (bool, error) { | |
| 30 | - | var count int | |
| 31 | - | err := a.DB.QueryRow("SELECT COUNT(*) FROM user WHERE id IS NOT NULL AND name != '' AND email != ''").Scan(&count) | |
| 32 | - | if err != nil { | |
| 33 | - | return false, err | |
| 34 | - | } | |
| 35 | - | if count > 0 { | |
| 36 | - | return true, nil | |
| 37 | - | } | |
| 38 | - | return false, nil | |
| 39 | - | } | |
| 40 | - | ||
| 41 | 30 | // RenderLoginPage renders the login.html template | |
| 42 | 31 | func RenderLoginPage(c echo.Context, a *app.App) error { | |
| 43 | 32 | ||
| 44 | 33 | // Compare the token | |
| 45 | 34 | compareGordonToken(c, a) | |
| 46 | 35 | ||
| 47 | - | // Check if there is already an admin in the db we cannot add another one so we redirect to the login page with a message | |
| 48 | - | adminExists, err := checkAdmin(a) | |
| 49 | - | if err != nil { | |
| 50 | - | return err | |
| 51 | - | } | |
| 52 | - | ||
| 53 | - | if adminExists { | |
| 54 | - | return c.HTML(http.StatusOK, "<h1>User already exists</h1>") | |
| 55 | - | } | |
| 56 | - | ||
| 57 | 36 | data := map[string]interface{}{ | |
| 58 | 37 | "Title": "Login", | |
| 59 | 38 | } | |
| @@ -121,33 +100,82 @@ func OAuthCallback(c echo.Context, a *app.App) error { | |||
| 121 | 100 | } | |
| 122 | 101 | ||
| 123 | 102 | browserInfo := c.Request().UserAgent() | |
| 124 | - | ||
| 125 | - | sessStr := fmt.Sprintf("%v", sess) | |
| 126 | - | fmt.Println(sessStr) | |
| 127 | - | ||
| 128 | - | // Check if the user already exists in the database | |
| 129 | - | userExists, err := queries.UserExists(a) | |
| 103 | + | // print github user info | |
| 104 | + | userInfo, err := githubGetUserDetails(c, a) | |
| 130 | 105 | if err != nil { | |
| 131 | 106 | return err | |
| 132 | 107 | } | |
| 133 | 108 | ||
| 109 | + | // Check if the user exists | |
| 110 | + | // if not, create it | |
| 111 | + | // if yes, update the access token and create or update the session | |
| 134 | 112 | // If the user does not exist, create it | |
| 113 | + | userExists, err := queries.CheckDBUserExists(a) | |
| 114 | + | if err != nil { | |
| 115 | + | return fmt.Errorf("could not check if user exists: %w", err) | |
| 116 | + | } | |
| 135 | 117 | if !userExists { | |
| 136 | - | err := queries.CreateUser(a, accessToken, browserInfo) | |
| 118 | + | err := queries.CreateUser(a, accessToken, browserInfo, userInfo) | |
| 137 | 119 | if err != nil { | |
| 138 | - | return err | |
| 120 | + | return fmt.Errorf("could not create user: %w", err) | |
| 139 | 121 | } | |
| 140 | 122 | } else { | |
| 141 | - | // If the user exists, update acccessToken and then create or update the session | |
| 142 | - | err := queries.UpdateAccessToken(a, accessToken) | |
| 143 | - | if err != nil { | |
| 144 | - | return err | |
| 145 | - | } | |
| 146 | 123 | ||
| 147 | - | err = queries.CreateOrUpdateSession(a, accessToken, browserInfo) | |
| 148 | - | if err != nil { | |
| 149 | - | return err | |
| 150 | - | } | |
| 151 | 124 | } | |
| 152 | 125 | return c.Redirect(http.StatusFound, "/admin") | |
| 153 | 126 | } | |
| 127 | + | func fetchGithubAPI(url string, authHeader string, result interface{}) error { | |
| 128 | + | req, err := http.NewRequest("GET", url, nil) | |
| 129 | + | if err != nil { | |
| 130 | + | return err | |
| 131 | + | } | |
| 132 | + | ||
| 133 | + | req.Header.Set("Authorization", authHeader) | |
| 134 | + | ||
| 135 | + | resp, err := http.DefaultClient.Do(req) | |
| 136 | + | if err != nil { | |
| 137 | + | return err | |
| 138 | + | } | |
| 139 | + | defer resp.Body.Close() | |
| 140 | + | ||
| 141 | + | if resp.StatusCode != http.StatusOK { | |
| 142 | + | return fmt.Errorf("got status code %d", resp.StatusCode) | |
| 143 | + | } | |
| 144 | + | ||
| 145 | + | body, err := io.ReadAll(resp.Body) | |
| 146 | + | if err != nil { | |
| 147 | + | return err | |
| 148 | + | } | |
| 149 | + | ||
| 150 | + | err = json.Unmarshal(body, &result) | |
| 151 | + | if err != nil { | |
| 152 | + | return err | |
| 153 | + | } | |
| 154 | + | ||
| 155 | + | return nil | |
| 156 | + | } | |
| 157 | + | ||
| 158 | + | func githubGetUserDetails(c echo.Context, a *app.App) (userInfo *queries.GithubUserInfo, err error) { | |
| 159 | + | accessToken := c.QueryParam("access_token") | |
| 160 | + | ||
| 161 | + | // Fetch user info | |
| 162 | + | err = fetchGithubAPI("https://api.github.com/user", "Bearer "+accessToken, &userInfo) | |
| 163 | + | if err != nil { | |
| 164 | + | return nil, err | |
| 165 | + | } | |
| 166 | + | // Fetch user emails | |
| 167 | + | var emailObjects []struct { | |
| 168 | + | Email string `json:"email"` | |
| 169 | + | } | |
| 170 | + | err = fetchGithubAPI("https://api.github.com/user/emails", "Bearer "+accessToken, &emailObjects) | |
| 171 | + | if err != nil { | |
| 172 | + | return nil, err | |
| 173 | + | } | |
| 174 | + | ||
| 175 | + | // Extract emails and populate the Emails field in userInfo | |
| 176 | + | for _, emailObj := range emailObjects { | |
| 177 | + | userInfo.Emails = append(userInfo.Emails, emailObj.Email) | |
| 178 | + | } | |
| 179 | + | ||
| 180 | + | return userInfo, nil | |
| 181 | + | } | |
| @@ -2,7 +2,9 @@ package handler | |||||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | "encoding/base64" | 4 | "encoding/base64" | ||
| 5 | + | "encoding/json" | |||
| 5 | "fmt" | 6 | "fmt" | ||
| 7 | + | "io" | |||
| 6 | "net/http" | 8 | "net/http" | ||
| 7 | "os" | 9 | "os" | ||
| 8 | 10 | ||||
| @@ -25,35 +27,12 @@ func compareGordonToken(c echo.Context, a *app.App) error { | |||||
| 25 | return nil | 27 | return nil | ||
| 26 | } | 28 | } | ||
| 27 | 29 | ||||
| 28 | - | // Check if there is already a user in the db which means that the admin is already setup | |||
| 29 | - | func checkAdmin(a *app.App) (bool, error) { | |||
| 30 | - | var count int | |||
| 31 | - | err := a.DB.QueryRow("SELECT COUNT(*) FROM user WHERE id IS NOT NULL AND name != '' AND email != ''").Scan(&count) | |||
| 32 | - | if err != nil { | |||
| 33 | - | return false, err | |||
| 34 | - | } | |||
| 35 | - | if count > 0 { | |||
| 36 | - | return true, nil | |||
| 37 | - | } | |||
| 38 | - | return false, nil | |||
| 39 | - | } | |||
| 40 | - | ||||
| 41 | // RenderLoginPage renders the login.html template | 30 | // RenderLoginPage renders the login.html template | ||
| 42 | func RenderLoginPage(c echo.Context, a *app.App) error { | 31 | func RenderLoginPage(c echo.Context, a *app.App) error { | ||
| 43 | 32 | ||||
| 44 | // Compare the token | 33 | // Compare the token | ||
| 45 | compareGordonToken(c, a) | 34 | compareGordonToken(c, a) | ||
| 46 | 35 | ||||
| 47 | - | // Check if there is already an admin in the db we cannot add another one so we redirect to the login page with a message | |||
| 48 | - | adminExists, err := checkAdmin(a) | |||
| 49 | - | if err != nil { | |||
| 50 | - | return err | |||
| 51 | - | } | |||
| 52 | - | ||||
| 53 | - | if adminExists { | |||
| 54 | - | return c.HTML(http.StatusOK, "<h1>User already exists</h1>") | |||
| 55 | - | } | |||
| 56 | - | ||||
| 57 | data := map[string]interface{}{ | 36 | data := map[string]interface{}{ | ||
| 58 | "Title": "Login", | 37 | "Title": "Login", | ||
| 59 | } | 38 | } | ||
| @@ -121,33 +100,82 @@ func OAuthCallback(c echo.Context, a *app.App) error { | |||||
| 121 | } | 100 | } | ||
| 122 | 101 | ||||
| 123 | browserInfo := c.Request().UserAgent() | 102 | browserInfo := c.Request().UserAgent() | ||
| 124 | - | 103 | + | // print github user info | |
| 125 | - | sessStr := fmt.Sprintf("%v", sess) | 104 | + | userInfo, err := githubGetUserDetails(c, a) |
| 126 | - | fmt.Println(sessStr) | |||
| 127 | - | ||||
| 128 | - | // Check if the user already exists in the database | |||
| 129 | - | userExists, err := queries.UserExists(a) | |||
| 130 | if err != nil { | 105 | if err != nil { | ||
| 131 | return err | 106 | return err | ||
| 132 | } | 107 | } | ||
| 133 | 108 | ||||
| 109 | + | // Check if the user exists | |||
| 110 | + | // if not, create it | |||
| 111 | + | // if yes, update the access token and create or update the session | |||
| 134 | // If the user does not exist, create it | 112 | // If the user does not exist, create it | ||
| 113 | + | userExists, err := queries.CheckDBUserExists(a) | |||
| 114 | + | if err != nil { | |||
| 115 | + | return fmt.Errorf("could not check if user exists: %w", err) | |||
| 116 | + | } | |||
| 135 | if !userExists { | 117 | if !userExists { | ||
| 136 | - | err := queries.CreateUser(a, accessToken, browserInfo) | 118 | + | err := queries.CreateUser(a, accessToken, browserInfo, userInfo) |
| 137 | if err != nil { | 119 | if err != nil { | ||
| 138 | - | return err | 120 | + | return fmt.Errorf("could not create user: %w", err) |
| 139 | } | 121 | } | ||
| 140 | } else { | 122 | } else { | ||
| 141 | - | // If the user exists, update acccessToken and then create or update the session | |||
| 142 | - | err := queries.UpdateAccessToken(a, accessToken) | |||
| 143 | - | if err != nil { | |||
| 144 | - | return err | |||
| 145 | - | } | |||
| 146 | 123 | ||||
| 147 | - | err = queries.CreateOrUpdateSession(a, accessToken, browserInfo) | |||
| 148 | - | if err != nil { | |||
| 149 | - | return err | |||
| 150 | - | } | |||
| 151 | } | 124 | } | ||
| 152 | return c.Redirect(http.StatusFound, "/admin") | 125 | return c.Redirect(http.StatusFound, "/admin") | ||
| 153 | } | 126 | } | ||
| 127 | + | func fetchGithubAPI(url string, authHeader string, result interface{}) error { | |||
| 128 | + | req, err := http.NewRequest("GET", url, nil) | |||
| 129 | + | if err != nil { | |||
| 130 | + | return err | |||
| 131 | + | } | |||
| 132 | + | ||||
| 133 | + | req.Header.Set("Authorization", authHeader) | |||
| 134 | + | ||||
| 135 | + | resp, err := http.DefaultClient.Do(req) | |||
| 136 | + | if err != nil { | |||
| 137 | + | return err | |||
| 138 | + | } | |||
| 139 | + | defer resp.Body.Close() | |||
| 140 | + | ||||
| 141 | + | if resp.StatusCode != http.StatusOK { | |||
| 142 | + | return fmt.Errorf("got status code %d", resp.StatusCode) | |||
| 143 | + | } | |||
| 144 | + | ||||
| 145 | + | body, err := io.ReadAll(resp.Body) | |||
| 146 | + | if err != nil { | |||
| 147 | + | return err | |||
| 148 | + | } | |||
| 149 | + | ||||
| 150 | + | err = json.Unmarshal(body, &result) | |||
| 151 | + | if err != nil { | |||
| 152 | + | return err | |||
| 153 | + | } | |||
| 154 | + | ||||
| 155 | + | return nil | |||
| 156 | + | } | |||
| 157 | + | ||||
| 158 | + | func githubGetUserDetails(c echo.Context, a *app.App) (userInfo *queries.GithubUserInfo, err error) { | |||
| 159 | + | accessToken := c.QueryParam("access_token") | |||
| 160 | + | ||||
| 161 | + | // Fetch user info | |||
| 162 | + | err = fetchGithubAPI("https://api.github.com/user", "Bearer "+accessToken, &userInfo) | |||
| 163 | + | if err != nil { | |||
| 164 | + | return nil, err | |||
| 165 | + | } | |||
| 166 | + | // Fetch user emails | |||
| 167 | + | var emailObjects []struct { | |||
| 168 | + | Email string `json:"email"` | |||
| 169 | + | } | |||
| 170 | + | err = fetchGithubAPI("https://api.github.com/user/emails", "Bearer "+accessToken, &emailObjects) | |||
| 171 | + | if err != nil { | |||
| 172 | + | return nil, err | |||
| 173 | + | } | |||
| 174 | + | ||||
| 175 | + | // Extract emails and populate the Emails field in userInfo | |||
| 176 | + | for _, emailObj := range emailObjects { | |||
| 177 | + | userInfo.Emails = append(userInfo.Emails, emailObj.Email) | |||
| 178 | + | } | |||
| 179 | + | ||||
| 180 | + | return userInfo, nil | |||
| 181 | + | } | |||