Skip to content

Commit

Permalink
feat(auth): add google & gitlab auth providers
Browse files Browse the repository at this point in the history
  • Loading branch information
scottmckendry committed Aug 24, 2024
1 parent 8b6f53b commit f30ba9b
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 74 deletions.
30 changes: 15 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,30 @@ Some reasons you might want to self-host mnemstart:

Luckily, self-hosting mnemstart is easy. The whole application (including the database) is contained in a single Docker container. You can run it on any machine that has Docker installed.

> [!IMPORTANT]
> To self-host mnemstart, you will need to register a new OAuth application with at least one of the supported providers:
>
> - [GitHub](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/creating-an-oauth-app)
> - [Discord](https://discord.com/developers/docs/topics/oauth2)
> - [Google](https://developers.google.com/identity/protocols/oauth2)
> - [GitLab](https://docs.gitlab.com/ee/integration/oauth_provider.html)
### 🐋 Using Docker Compose

1. Register a new OAuth application with GitHub and/or Discord. You will need to provide a callback URL in the format `https://your-domain.com[:PORT]/auth/[provider]/callback` where `[provider]` is either `github` or `discord`. Make a note of the client ID and client secret.
2. Create an empty directory to store the configuration file and database.
3. In the empty directory, create an `.env` file with the following contents:

```env
# Required - replace with your own values
# Required - at least one OAuth provider is required
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
DISCORD_CLIENT_ID=your-discord-client-id
DISCORD_CLIENT_SECRET=your-discord-client-secret
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GITLAB_CLIENT_ID=your-gitlab-client-id
GITLAB_CLIENT_SECRET=your-gitlab-client-secret
# Optional
PUBLIC_HOST=https://your-domain.com # Defaults to http://localhost
Expand Down Expand Up @@ -90,19 +102,7 @@ services:
### 🐋 Using Docker (Recommended)

1. Clone the repository and navigate to the root directory.
2. Create an `.env` file with the following contents:

```env
GITHUB_CLIENT_ID=your-github-client-id
GITHUB_CLIENT_SECRET=your-github-client-secret
DISCORD_CLIENT_ID=your-discord-client-id
DISCORD_CLIENT_SECRET=your-discord-client-secret
```

> [!NOTE]
> Only one valid OAuth provider is required to run the application. You can leave the other provider's client ID and secret blank if you wish.
> Documentation for registering a new OAuth application with GitHub can be found [here](https://docs.github.com/en/developers/apps/building-oauth-apps/creating-an-oauth-app) and Discord [here](https://discord.com/developers/docs/topics/oauth2).

2. Create an `.env` file and populate at least one OAuth provider. See the example above in **Self-Hosting**.
3. Run `docker-compose up` to start the development server. The application will be available at `http://localhost:3000`.

### 🚀 Without Docker
Expand All @@ -116,7 +116,7 @@ DISCORD_CLIENT_SECRET=your-discord-client-secret
**Steps:**

1. Clone the repository and navigate to the root directory.
2. Create an `.env` file - see above for contents.
2. Create an `.env` file with an OAuth provider. See the example above in **Self-Hosting**.
3. Run `templ generate` to ensure all `_templ.go` files are up to date.
4. Run `air` to start the development server. The application will be available at `http://localhost:3000`.

Expand Down
17 changes: 17 additions & 0 deletions auth/auth.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package auth

import (
"encoding/gob"
"fmt"
"log"
"net/http"
Expand All @@ -10,12 +11,18 @@ import (
"github.com/markbates/goth/gothic"
"github.com/markbates/goth/providers/discord"
"github.com/markbates/goth/providers/github"
"github.com/markbates/goth/providers/gitlab"
"github.com/markbates/goth/providers/google"

"github.com/scottmckendry/mnemstart/config"
)

type AuthService struct{}

func init() {
gob.Register(map[string]interface{}{})
}

func NewAuthService(store sessions.Store) *AuthService {
gothic.Store = store

Expand All @@ -30,6 +37,16 @@ func NewAuthService(store sessions.Store) *AuthService {
config.Envs.DiscordClientSecret,
buildCallbackURL("discord"), "identify", "email",
),
google.New(
config.Envs.GoogleClientID,
config.Envs.GoogleClientSecret,
buildCallbackURL("google"), "email", "profile",
),
gitlab.New(
config.Envs.GitlabClientID,
config.Envs.GitlabClientSecret,
buildCallbackURL("gitlab"), "read_user", "email",
),
)

return &AuthService{}
Expand Down
46 changes: 13 additions & 33 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,18 @@ type Config struct {
GithubClientSecret string
DiscordClientID string
DiscordClientSecret string
GoogleClientID string
GoogleClientSecret string
GitlabClientID string
GitlabClientSecret string
}

var Envs = initConfig()

func initConfig() *Config {
err := godotenv.Load()
if err != nil {
log.Print("Error loading .env file")
log.Println("Creating empty .env file, with default values for required variables")
createEmptyEnvFile()
err = godotenv.Load()
if err != nil {
log.Fatal("Error loading .env file")
}
log.Print("No .env file found. Using default environment variables.")
}

return &Config{
Expand All @@ -51,10 +49,14 @@ func initConfig() *Config {
CookiesAuthAgeInSeconds: getEnvAsInt("COOKIES_AUTH_AGE_IN_SECONDS", thirtyDaysInSeconds),
CookiesAuthIsSecure: getEnvAsBool("COOKIES_AUTH_IS_SECURE", false),
CookiesAuthIsHttpOnly: getEnvAsBool("COOKIES_AUTH_IS_HTTP_ONLY", true),
GithubClientID: getEnvOrPanic("GITHUB_CLIENT_ID"),
GithubClientSecret: getEnvOrPanic("GITHUB_CLIENT_SECRET"),
DiscordClientID: getEnvOrPanic("DISCORD_CLIENT_ID"),
DiscordClientSecret: getEnvOrPanic("DISCORD_CLIENT_SECRET"),
GithubClientID: getEnv("GITHUB_CLIENT_ID", ""),
GithubClientSecret: getEnv("GITHUB_CLIENT_SECRET", ""),
DiscordClientID: getEnv("DISCORD_CLIENT_ID", ""),
DiscordClientSecret: getEnv("DISCORD_CLIENT_SECRET", ""),
GoogleClientID: getEnv("GOOGLE_CLIENT_ID", ""),
GoogleClientSecret: getEnv("GOOGLE_CLIENT_SECRET", ""),
GitlabClientID: getEnv("GITLAB_CLIENT_ID", ""),
GitlabClientSecret: getEnv("GITLAB_CLIENT_SECRET", ""),
}
}

Expand All @@ -65,13 +67,6 @@ func getEnv(key, fallback string) string {
return fallback
}

func getEnvOrPanic(key string) string {
if value, ok := os.LookupEnv(key); ok {
return value
}
panic("Missing required environment variable: " + key)
}

func getEnvAsInt(key string, fallback int) int {
if value, ok := os.LookupEnv(key); ok {
if intValue, err := strconv.Atoi(value); err == nil {
Expand All @@ -89,18 +84,3 @@ func getEnvAsBool(key string, fallback bool) bool {
}
return fallback
}

func createEmptyEnvFile() {
f, err := os.Create(".env")
if err != nil {
log.Fatal(err)
}
defer f.Close()

_, err = f.WriteString(
"GITHUB_CLIENT_ID=\nGITHUB_CLIENT_SECRET=\nDISCORD_CLIENT_ID=\nDISCORD_CLIENT_SECRET=\n",
)
if err != nil {
log.Fatal(err)
}
}
21 changes: 0 additions & 21 deletions config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,6 @@ func TestGetEnv(t *testing.T) {
})
}

func TestGetEnvOrPanic(t *testing.T) {
os.Setenv("TEST_ENV", "test_value")

result := getEnvOrPanic("TEST_ENV")
if result != "test_value" {
t.Errorf("Expected 'test_value', got '%s'", result)
}

defer func() {
if r := recover(); r == nil {
t.Errorf("Expected a panic for non-existent environment variable")
}
}()

getEnvOrPanic("NON_EXISTENT_ENV")

t.Cleanup(func() {
os.Unsetenv("TEST_ENV")
})
}

func TestGetEnvAsInt(t *testing.T) {
os.Setenv("TEST_ENV", "123")

Expand Down
31 changes: 27 additions & 4 deletions data/data.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ type User struct {
Email string
DiscordID string
GithubID string
GoogleID string
GitlabID string
}

type Mapping struct {
Expand Down Expand Up @@ -62,12 +64,27 @@ func appendProviderID(provider string, userId string, user *User) {
user.DiscordID = userId
case "github":
user.GithubID = userId
case "google":
user.GoogleID = userId
case "gitlab":
user.GitlabID = userId
}
}

func getUser(db *sql.DB, user *User) error {
row := db.QueryRow("SELECT * FROM users WHERE email = ?", user.Email)
err := row.Scan(&user.ID, &user.Name, &user.Email, &user.DiscordID, &user.GithubID)
row := db.QueryRow(
"SELECT id, name, email, discord_id, github_id, google_id, gitlab_id FROM users WHERE email = ?",
user.Email,
)
err := row.Scan(
&user.ID,
&user.Name,
&user.Email,
&user.DiscordID,
&user.GithubID,
&user.GoogleID,
&user.GitlabID,
)
if err != nil {
return err
}
Expand All @@ -77,11 +94,13 @@ func getUser(db *sql.DB, user *User) error {

func createUser(db *sql.DB, user *User) error {
_, err := db.Exec(
"INSERT INTO users (name, email, discord_id, github_id) VALUES (?, ?, ?, ?)",
"INSERT INTO users (name, email, discord_id, github_id, google_id, gitlab_id) VALUES (?, ?, ?, ?, ?, ?)",
user.Name,
user.Email,
user.DiscordID,
user.GithubID,
user.GoogleID,
user.GitlabID,
)
if err != nil {
return err
Expand All @@ -99,12 +118,16 @@ func updateUser(db *sql.DB, user *User) error {
ELSE name
END,
discord_id = ?,
github_id = ?
github_id = ?,
google_id = ?,
gitlab_id = ?
WHERE email = ?
`,
user.Name,
user.DiscordID,
user.GithubID,
user.GoogleID,
user.GitlabID,
user.Email,
)
if err != nil {
Expand Down
4 changes: 3 additions & 1 deletion data/libsql.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ func generateSchema(db *sql.DB) error {
name TEXT,
email TEXT,
discord_id TEXT,
github_id TEXT
github_id TEXT,
google_id TEXT,
gitlab_id TEXT
);
`)
if err != nil {
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ require (
)

require (
cloud.google.com/go/compute v1.20.1 // indirect
cloud.google.com/go/compute/metadata v0.2.3 // indirect
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/coder/websocket v1.8.12 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
cloud.google.com/go/compute v1.20.1 h1:6aKEtlUiwEpJzM001l0yFkpXmUVXaN8W+fbkb2AZNbg=
cloud.google.com/go/compute v1.20.1/go.mod h1:4tCnrn48xsqlwSAiLf1HXMQk8CONslYbdiEZc9FEIbM=
cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY=
cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA=
github.com/a-h/templ v0.2.747 h1:D0dQ2lxC3W7Dxl6fxQ/1zZHBQslSkTSvl5FxP/CfdKg=
github.com/a-h/templ v0.2.747/go.mod h1:69ObQIbrcuwPCU32ohNaWce3Cb7qM5GMiqN1K+2yop4=
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
Expand Down

0 comments on commit f30ba9b

Please sign in to comment.