diff --git a/nats-w-auth/github.go b/nats-w-auth/github.go index 9d52a45..1c43f1c 100644 --- a/nats-w-auth/github.go +++ b/nats-w-auth/github.go @@ -5,15 +5,21 @@ import ( "encoding/json" "fmt" "io" + "nats-w-auth/internal/users" "net/http" "os" + "strconv" ) // https://docs.github.com/en/rest/users/users?apiVersion=2022-11-28#get-a-user type GitHubUser struct { + Name string + Avatar_url string + ID int } func githubSuccessHandler(w http.ResponseWriter, r *http.Request) { + provider := "github" code := r.URL.Query().Get("code") fmt.Println("callback from github", code) token, err := tokenForCode(code) @@ -22,13 +28,39 @@ func githubSuccessHandler(w http.ResponseWriter, r *http.Request) { return } fmt.Println("access token", token) - user, err := getGithubUser(token) + ghUser, err := getGithubUser(token) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } - fmt.Println("user", user) - sessionManager.Put(r.Context(), "pie", "pizza") + fmt.Println("user", ghUser) + sessionManager.Put(r.Context(), "pie", ghUser.Name) + user, err := userManager.GetOAuthUser(r.Context(), provider, strconv.Itoa(ghUser.ID)) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if user == nil { + // TODO: Create core user + // TODO: Create user/oauth connection + fmt.Println("we should create user", user) + user, err = userManager.CreateUser(r.Context(), users.UserModelInfo{ + Name: ghUser.Name, + AvatarURL: ghUser.Avatar_url, + }) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + err = userManager.LinkUserProvider(r.Context(), user, provider, strconv.Itoa(ghUser.ID)) + sessionManager.Put(r.Context(), "user_id", user.ID) + sessionManager.Put(r.Context(), "name", user.Name) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + // TODO: Find or create user according to GitHub user_id // TODO: Set (our) user_id, user on session @@ -76,7 +108,7 @@ func tokenForCode(code string) (string, error) { } -func getGithubUser(token string) (map[string]any, error) { +func getGithubUser(token string) (*GitHubUser, error) { url := "https://api.github.com/user" req, err := http.NewRequest("GET", url, nil) @@ -98,12 +130,14 @@ func getGithubUser(token string) (map[string]any, error) { } body, _ := io.ReadAll(resp.Body) + //fmt.Println("user data", body) - var user map[string]any + var user GitHubUser if err := json.Unmarshal(body, &user); err != nil { return nil, err } + fmt.Println("github user", user) - return user, nil + return &user, nil } diff --git a/nats-w-auth/go.mod b/nats-w-auth/go.mod index aa2e9c0..4acae47 100644 --- a/nats-w-auth/go.mod +++ b/nats-w-auth/go.mod @@ -6,6 +6,7 @@ require ( github.com/alexedwards/scs/v2 v2.9.0 // indirect github.com/coreos/go-oidc/v3 v3.14.1 // indirect github.com/go-jose/go-jose/v4 v4.0.5 // indirect + github.com/gofrs/uuid/v5 v5.3.2 // indirect github.com/google/go-tpm v0.9.5 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/minio/highwayhash v1.0.3 // indirect diff --git a/nats-w-auth/go.sum b/nats-w-auth/go.sum index 2f730a4..68b3ef1 100644 --- a/nats-w-auth/go.sum +++ b/nats-w-auth/go.sum @@ -4,6 +4,8 @@ github.com/coreos/go-oidc/v3 v3.14.1 h1:9ePWwfdwC4QKRlCXsJGou56adA/owXczOzwKdOum github.com/coreos/go-oidc/v3 v3.14.1/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU= github.com/go-jose/go-jose/v4 v4.0.5 h1:M6T8+mKZl/+fNNuFHvGIzDz7BTLQPIounk/b9dw3AaE= github.com/go-jose/go-jose/v4 v4.0.5/go.mod h1:s3P1lRrkT8igV8D9OjyL4WRyHvjB6a4JSllnOrmmBOA= +github.com/gofrs/uuid/v5 v5.3.2 h1:2jfO8j3XgSwlz/wHqemAEugfnTlikAYHhnqQ8Xh4fE0= +github.com/gofrs/uuid/v5 v5.3.2/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8= github.com/google/go-tpm v0.9.5 h1:ocUmnDebX54dnW+MQWGQRbdaAcJELsa6PqZhJ48KwVU= github.com/google/go-tpm v0.9.5/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= diff --git a/nats-w-auth/http.go b/nats-w-auth/http.go index e2efd0b..96fa9fd 100644 --- a/nats-w-auth/http.go +++ b/nats-w-auth/http.go @@ -2,6 +2,7 @@ package main import ( "embed" + "fmt" "io/fs" "log" "net/http" @@ -34,7 +35,9 @@ func httpServe() { mux.HandleFunc("/oauth/success", githubSuccessHandler) mux.HandleFunc("/", fallbackHandler) + fmt.Println("Listening on http://localhost:3000") http.ListenAndServe(":3000", sessionManager.LoadAndSave(mux)) + } // 1. See if we have a static file with the same name. Exclude .gohtml and .go files. @@ -66,7 +69,9 @@ func fallbackHandler(w http.ResponseWriter, r *http.Request) { "GH_CLIENT_ID": os.Getenv("GH_CLIENT_ID"), } tctx["Session"] = map[string]string{ - "pie": pieIs, + "pie": pieIs, + "name": sessionManager.GetString(r.Context(), "name"), + "user_id": sessionManager.GetString(r.Context(), "user_id"), } templateName := pathToTemplateName(r.URL.Path) diff --git a/nats-w-auth/internal/users/users.go b/nats-w-auth/internal/users/users.go index 985f96b..19e0542 100644 --- a/nats-w-auth/internal/users/users.go +++ b/nats-w-auth/internal/users/users.go @@ -6,6 +6,7 @@ import ( "encoding/json" "log" + "github.com/gofrs/uuid/v5" "github.com/nats-io/nats.go" "github.com/nats-io/nats.go/jetstream" ) @@ -39,8 +40,13 @@ func New(clientURL string) *UserManager { } type UserModel struct { - ID string - Name string + ID string + UserModelInfo +} + +type UserModelInfo struct { + Name string + AvatarURL string } type OAuthModel struct { @@ -64,8 +70,12 @@ func (um *UserManager) GetOAuthUser(ctx context.Context, provider, puid string) } // Grab user_id mapping from provider, if any - val, err := kv.Get(ctx, provider+PROVIDER_SEPARATOR+puid) + val, err := kv.Get(ctx, oauthKey(provider, puid)) if err != nil { + if err == jetstream.ErrKeyNotFound { + // No key found, but it's ok + return nil, nil + } return nil, err } userID := string(val.Value()) @@ -78,6 +88,29 @@ func (um *UserManager) GetOAuthUser(ctx context.Context, provider, puid string) return user, nil } +func (um *UserManager) CreateUser(ctx context.Context, u UserModelInfo) (*UserModel, error) { + kv, err := um.js.KeyValue(ctx, USER_BUCKET) + if err != nil { + return nil, err + } + uuid := uuid.Must(uuid.NewV4()) + umm := UserModel{ + ID: uuid.String(), + UserModelInfo: u, + } + jsonu, err := json.Marshal(umm) + if err != nil { + return nil, err + } + _, err = kv.Create(ctx, uuid.String(), jsonu) + if err != nil { + return nil, err + } + + return &umm, nil + +} + func (um *UserManager) GetUser(ctx context.Context, userID string) (*UserModel, error) { kv, err := um.js.KeyValue(ctx, USER_BUCKET) if err != nil { @@ -116,3 +149,30 @@ func (um *UserManager) setupBuckets() { log.Fatal(err) } } + +func oauthKey(provider, puid string) string { + return provider + PROVIDER_SEPARATOR + puid +} + +func (um *UserManager) LinkUserProvider(ctx context.Context, user *UserModel, provider, puid string) error { + kv, err := um.js.KeyValue(ctx, OAUTH_BUCKET) + if err != nil { + return err + } + _, err = kv.Get(ctx, oauthKey(provider, puid)) + if err != nil { + if err == jetstream.ErrKeyNotFound { + // No link found, create one + _, err = kv.Create(ctx, oauthKey(provider, puid), []byte(user.ID)) + if err != nil { + return err + } + } else { + return err + } + + } + + return nil + +} diff --git a/nats-w-auth/mise.toml b/nats-w-auth/mise.toml index 7374c8e..770e0de 100644 --- a/nats-w-auth/mise.toml +++ b/nats-w-auth/mise.toml @@ -7,7 +7,7 @@ go = "latest" _.file = '.env' [tasks."dev:go"] -sources = ["**/*.go", "**/*.gohtml"] +sources = ["**/*.go", "**/*.gohtml", "**/*.css"] run = "go run ." diff --git a/nats-w-auth/nats.go b/nats-w-auth/nats.go index afb1428..446c98b 100644 --- a/nats-w-auth/nats.go +++ b/nats-w-auth/nats.go @@ -21,7 +21,7 @@ func setupNats() { go natsd.Start() if !natsd.ReadyForConnections(4 * time.Second) { - panic("not ready for connection") + panic("nats server not ready at expected time. Is there another instance running?") } nc, err := nats.Connect(natsd.ClientURL()) diff --git a/nats-w-auth/www/_fragments/account-widget.gohtml b/nats-w-auth/www/_fragments/account-widget.gohtml index 2779f05..db82b98 100644 --- a/nats-w-auth/www/_fragments/account-widget.gohtml +++ b/nats-w-auth/www/_fragments/account-widget.gohtml @@ -1,3 +1,5 @@
Login with GitHub +
+

Name: '{{ .Session.Name }}'

diff --git a/nats-w-auth/www/index.gohtml b/nats-w-auth/www/index.gohtml index 62bfab7..17ef308 100644 --- a/nats-w-auth/www/index.gohtml +++ b/nats-w-auth/www/index.gohtml @@ -1,6 +1,7 @@ NATS Web UI + {{template "_fragments/account-widget" .}} diff --git a/nats-w-auth/www/main.css b/nats-w-auth/www/main.css new file mode 100644 index 0000000..99c6dde --- /dev/null +++ b/nats-w-auth/www/main.css @@ -0,0 +1,9 @@ +/* A dark theme */ +body { + background-color: #333; + color: #fff; +} + +a { + color: #eee; +}