Skip to content
This repository was archived by the owner on Jun 11, 2025. It is now read-only.
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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,3 @@ nohup.out

.kl/secret*
__debug_bin
cmd/struct-to-graphql
9 changes: 9 additions & 0 deletions .tools/__http__/auth/auth.graphql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,12 @@ query: |
variables:
token:

---
query: |+
query ListOAuthProviders {
auth_listOAuthProviders {
provider
enabled
}
}
---
4 changes: 2 additions & 2 deletions .tools/gqlenv.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
mode: dev
map:
dev:
url: http://localhost:3000/query
# url: http://gateway-api.kl-core.svc.cluster.local/query
# url: http://localhost:3000/query
url: http://gateway-api.kl-core.svc.cluster.local/query
headers:
cookie: hotspot-session=ses-ykthtwm9so4cnklyocuckywjx6ro; domain=.kloudlite.io; path=/; HttpOnly; secure; SameSite=None;kloudlite-account=kloudlite-dev;kloudlite-cluster=sample2;
12 changes: 12 additions & 0 deletions .tools/nvim/dap/go.lua
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
local dap = require("dap")

dap.configurations.go = {
{
type = "go",
name = "Debug auth-api",
request = "launch",
program = vim.g.root_dir .. "/apps/auth",
args = { "--dev" },
console = "externalTerminal",
-- externalTerminal = true,
envFile = {
vim.g.root_dir .. "/apps/auth" .. "/.secrets/env",
},
},
{
type = "go",
name = "Debug infra-api",
Expand Down
3 changes: 1 addition & 2 deletions apps/auth/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,7 @@ ENV CGO_ENABLED=0
RUN GOOS=linux GOARCH=amd64 go build -tags musl -o /tmp/bin/$APP ./main.go
RUN chmod +x /tmp/bin/$APP

#FROM gcr.io/distroless/static:nonroot
FROM golang:1.18.3-alpine3.16
FROM gcr.io/distroless/static:nonroot
USER 1001:1001
ARG APP
COPY --from=base /tmp/bin/$APP /auth
Expand Down
22 changes: 18 additions & 4 deletions apps/auth/Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ tasks:
run:
cmds:
- nodemon -e go --signal SIGKILL --exec 'go run -tags dynamic main.go --dev || exit 1'

gql:
sources:
- graph/schema.graphqls
Expand All @@ -14,11 +15,23 @@ tasks:
cmds:
- go run github.com/99designs/gqlgen generate

build:
env:
CGO_ENABLED: 0
preconditions:
- sh: '[ -n "{{.Out}}" ]'
msg: var Out must have a value
cmds:
- go build -ldflags="-s -w" -o {{.Out}}
- upx {{.Out}}


docker-build:
vars:
# IMAGE: registry.kloudlite.io/kloudlite/auth:v2
APP: auth
IMAGE: registry.kloudlite.io/kloudlite/{{.EnvName}}/{{.APP}}-api:{{.Tag}}
# IMAGE: registry.kloudlite.io/kloudlite/{{.EnvName}}/{{.APP}}-api:{{.Tag}}
IMAGE: ghcr.io/kloudlite/apis/{{.APP}}:{{.Tag}}
preconditions:
- sh: '[[ -n "{{.Tag}}" ]]'
msg: 'var Tag must have a value'
Expand All @@ -37,7 +50,8 @@ tasks:
msg: 'var Tag must have a value'
vars:
APP: auth
IMAGE: registry.kloudlite.io/kloudlite/{{.EnvName}}/{{.APP}}-api:{{.Tag}}
# IMAGE: registry.kloudlite.io/kloudlite/{{.EnvName}}/{{.APP}}-api:{{.Tag}}
IMAGE: ghcr.io/kloudlite/apis/{{.APP}}:{{.Tag}}
env:
CGO_ENABLED: 0
GOOS: linux
Expand All @@ -58,8 +72,8 @@ tasks:

cat $tDir/Dockerfile.base | sed "4 i COPY --from=local-builder ./{{.APP}} /{{.APP}}" > $tDir/Dockerfile
cat $tDir/Dockerfile

CGO_ENABLED=0 go build -o $tDir/{{.APP}} .
task build Out=$tDir/{{.APP}}

docker buildx build -f $tDir/Dockerfile -t {{.IMAGE}} . --build-context local-builder=${tDir}
docker push {{.IMAGE}}
Expand Down
50 changes: 8 additions & 42 deletions apps/auth/internal/app/main.go → apps/auth/internal/app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,59 +6,24 @@ import (
"github.com/gofiber/fiber/v2"
"go.uber.org/fx"
"google.golang.org/grpc"

"kloudlite.io/apps/auth/internal/app/graph"
"kloudlite.io/apps/auth/internal/app/graph/generated"
"kloudlite.io/apps/auth/internal/domain"
"kloudlite.io/apps/auth/internal/env"
"kloudlite.io/common"
"kloudlite.io/constants"
"kloudlite.io/grpc-interfaces/kloudlite.io/rpc/auth"
"kloudlite.io/grpc-interfaces/kloudlite.io/rpc/comms"
"kloudlite.io/pkg/cache"
"kloudlite.io/pkg/config"
httpServer "kloudlite.io/pkg/http-server"
"kloudlite.io/pkg/repos"
)

type Env struct {
CookieDomain string `env:"COOKIE_DOMAIN" required:"true"`
GithubWebhookUrl string `env:"GITHUB_WEBHOOK_URL" required:"true"`
GitlabWebhookUrl string `env:"GITLAB_WEBHOOK_URL" required:"true"`

GithubClientId string `env:"GITHUB_CLIENT_ID" required:"true"`
GithubClientSecret string `env:"GITHUB_CLIENT_SECRET" required:"true"`
GithubCallbackUrl string `env:"GITHUB_CALLBACK_URL" required:"true"`
GithubAppId string `env:"GITHUB_APP_ID" required:"true"`
GithubAppPKFile string `env:"GITHUB_APP_PK_FILE" required:"true"`
GithubScopes string `env:"GITHUB_SCOPES" required:"true"`

GitlabClientId string `env:"GITLAB_CLIENT_ID" required:"true"`
GitlabClientSecret string `env:"GITLAB_CLIENT_SECRET" required:"true"`
GitlabCallbackUrl string `env:"GITLAB_CALLBACK_URL" required:"true"`
GitlabScopes string `env:"GITLAB_SCOPES" required:"true"`

GoogleClientId string `env:"GOOGLE_CLIENT_ID" required:"true"`
GoogleClientSecret string `env:"GOOGLE_CLIENT_SECRET" required:"true"`
GoogleCallbackUrl string `env:"GOOGLE_CALLBACK_URL" required:"true"`
GoogleScopes string `env:"GOOGLE_SCOPES" required:"true"`
}

func (env *Env) GoogleConfig() (clientId string, clientSecret string, callbackUrl string) {
return env.GoogleClientId, env.GoogleClientSecret, env.GoogleCallbackUrl
}

func (env *Env) GitlabConfig() (clientId string, clientSecret string, callbackUrl string) {
return env.GitlabClientId, env.GitlabClientSecret, env.GitlabCallbackUrl
}

func (env *Env) GithubConfig() (clientId, clientSecret, callbackUrl, githubAppId, githubAppPKFile string) {
return env.GithubClientId, env.GithubClientSecret, env.GithubCallbackUrl, env.GithubAppId, env.GithubAppPKFile
}

type CommsClientConnection *grpc.ClientConn

var Module = fx.Module(
"app",
config.EnvFx[Env](),
repos.NewFxMongoRepo[*domain.User]("users", "usr", domain.UserIndexes),
repos.NewFxMongoRepo[*domain.AccessToken]("access_tokens", "tkn", domain.AccessTokenIndexes),
repos.NewFxMongoRepo[*domain.RemoteLogin]("remote_logins", "rlgn", domain.RemoteTokenIndexes),
Expand All @@ -82,8 +47,8 @@ var Module = fx.Module(
},
),

fx.Invoke(func(server *fiber.App, cacheClient cache.Client, env *Env) {
sessionMiddleware := httpServer.NewSessionMiddleware[*common.AuthSession](cacheClient, constants.CookieName, env.CookieDomain, constants.CacheSessionPrefix)
fx.Invoke(func(server *fiber.App, cacheClient cache.Client, ev *env.Env) {
sessionMiddleware := httpServer.NewSessionMiddleware[*common.AuthSession](cacheClient, constants.CookieName, ev.CookieDomain, constants.CacheSessionPrefix)
// INFO: (route: `/.check/logged-in`) is supposed to be used by nginx, as authentication url for other kloudlite services,
// where this api acts as authentication provider
server.Get("/.check/logged-in", sessionMiddleware, func(c *fiber.Ctx) error {
Expand All @@ -99,22 +64,23 @@ var Module = fx.Module(
func(
server *fiber.App,
d domain.Domain,
env *Env,
ev *env.Env,
cacheClient cache.Client,
) {
schema := generated.NewExecutableSchema(
generated.Config{Resolvers: graph.NewResolver(d)},
generated.Config{Resolvers: graph.NewResolver(d, ev)},
)
httpServer.SetupGQLServer(
server, schema,
httpServer.NewSessionMiddleware[*common.AuthSession](
cacheClient,
constants.CookieName,
env.CookieDomain,
ev.CookieDomain,
constants.CacheSessionPrefix,
),
)
},
),

domain.Module,
)
43 changes: 30 additions & 13 deletions apps/auth/internal/app/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package app

import (
"context"
"io/ioutil"
"fmt"
"net/http"
"os"
"strconv"
"strings"
"time"
Expand All @@ -13,22 +14,30 @@ import (
"golang.org/x/oauth2"
oauthGithub "golang.org/x/oauth2/github"
"kloudlite.io/apps/auth/internal/domain"
"kloudlite.io/apps/auth/internal/env"
"kloudlite.io/pkg/errors"
fn "kloudlite.io/pkg/functions"
)

type githubI struct {
enabled bool
cfg *oauth2.Config
ghCli *github.Client
ghCliForUser func(ctx context.Context, token *oauth2.Token) *github.Client
webhookUrl string
}

func (gh *githubI) GetOAuthToken(ctx context.Context, token *oauth2.Token) (*oauth2.Token, error) {
if !gh.enabled {
return nil, fmt.Errorf("github oauth is disabled")
}
return gh.cfg.TokenSource(ctx, token).Token()
}

func (gh *githubI) Authorize(_ context.Context, state string) (string, error) {
if !gh.enabled {
return "", fmt.Errorf("github oauth is disabled")
}
csrfToken := fn.Must(fn.CleanerNanoid(32))
b64state, err := fn.Json.ToB64Url(map[string]string{"csrf": csrfToken, "state": state})
if err != nil {
Expand All @@ -41,6 +50,10 @@ func (gh *githubI) Authorize(_ context.Context, state string) (string, error) {
}

func (gh *githubI) Callback(ctx context.Context, code, state string) (*github.User, *oauth2.Token, error) {
if !gh.enabled {
return nil, nil, fmt.Errorf("github oauth is disabled")
}

token, err := gh.cfg.Exchange(ctx, code)
if err != nil {
return nil, nil, errors.NewEf(err, "could not exchange the token")
Expand All @@ -55,6 +68,10 @@ func (gh *githubI) Callback(ctx context.Context, code, state string) (*github.Us
}

func (gh *githubI) GetPrimaryEmail(ctx context.Context, token *oauth2.Token) (string, error) {
if !gh.enabled {
return "", fmt.Errorf("github oauth is disabled")
}

emails, _, err := gh.ghCliForUser(ctx, token).Users.ListEmails(
ctx, &github.ListOptions{
Page: 1,
Expand All @@ -74,25 +91,24 @@ func (gh *githubI) GetPrimaryEmail(ctx context.Context, token *oauth2.Token) (st
return "", errors.Newf("no primary email could be found for this user, among first 100 emails provided by github")
}

type GithubOAuth interface {
GithubConfig() (clientId, clientSecret, callbackUrl, githubAppId, githubAppPKFile string)
}
func fxGithub(ev *env.Env) domain.Github {
if !ev.OAuth2Enabled || !ev.OAuth2GithubEnabled {
return &githubI{enabled: false}
}

func fxGithub(env *Env) domain.Github {
clientId, clientSecret, callbackUrl, ghAppId, ghAppPKFile := env.GithubConfig()
cfg := oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
ClientID: ev.GithubClientId,
ClientSecret: ev.GithubClientSecret,
Endpoint: oauthGithub.Endpoint,
RedirectURL: callbackUrl,
Scopes: strings.Split(env.GithubScopes, ","),
RedirectURL: ev.GithubCallbackUrl,
Scopes: strings.Split(ev.GithubScopes, ","),
}
privatePem, err := ioutil.ReadFile(ghAppPKFile)
privatePem, err := os.ReadFile(ev.GithubAppPKFile)
if err != nil {
panic(errors.NewEf(err, "reading github app PK file"))
}

appId, _ := strconv.ParseInt(ghAppId, 10, 64)
appId, _ := strconv.ParseInt(ev.GithubAppId, 10, 64)
itr, err := ghinstallation.NewAppsTransport(http.DefaultTransport, appId, privatePem)
if err != nil {
panic(errors.NewEf(err, "creating app transport"))
Expand All @@ -106,9 +122,10 @@ func fxGithub(env *Env) domain.Github {
ghCli := github.NewClient(&http.Client{Transport: itr, Timeout: time.Second * 30})

return &githubI{
enabled: true,
cfg: &cfg,
ghCli: ghCli,
ghCliForUser: ghCliForUser,
webhookUrl: env.GithubWebhookUrl,
webhookUrl: ev.GithubWebhookUrl,
}
}
31 changes: 23 additions & 8 deletions apps/auth/internal/app/gitlab.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,40 @@ package app

import (
"context"
"fmt"
"strings"

"github.com/xanzy/go-gitlab"
"golang.org/x/oauth2"
oauthGitlab "golang.org/x/oauth2/gitlab"
"kloudlite.io/apps/auth/internal/domain"
"kloudlite.io/apps/auth/internal/env"
"kloudlite.io/pkg/errors"
)

type gitlabI struct {
cfg *oauth2.Config
enabled bool
cfg *oauth2.Config
}

func (gl *gitlabI) GetOAuthToken(ctx context.Context, token *oauth2.Token) (*oauth2.Token, error) {
if !gl.enabled {
return nil, fmt.Errorf("gitlab oauth is disabled")
}
return gl.cfg.TokenSource(ctx, token).Token()
}

func (gl *gitlabI) Authorize(_ context.Context, state string) (string, error) {
if !gl.enabled {
return "", fmt.Errorf("gitlab oauth is disabled")
}
return gl.cfg.AuthCodeURL(state), nil
}

func (gl *gitlabI) Callback(ctx context.Context, code string, state string) (*gitlab.User, *oauth2.Token, error) {
if !gl.enabled {
return nil, nil, fmt.Errorf("gitlab oauth is disabled")
}
token, err := gl.cfg.Exchange(ctx, code)
if err != nil {
return nil, nil, errors.NewEf(err, "could not exchange the token")
Expand All @@ -44,15 +56,18 @@ type GitlabOAuth interface {
GitlabConfig() (clientId, clientSecret, callbackUrl string)
}

func fxGitlab(env *Env) domain.Gitlab {
clientId, clientSecret, callbackUrl := env.GitlabConfig()
func fxGitlab(ev *env.Env) domain.Gitlab {
if !ev.OAuth2Enabled || !ev.OAuth2GitlabEnabled {
return &gitlabI{enabled: false, cfg: nil}
}

cfg := oauth2.Config{
ClientID: clientId,
ClientSecret: clientSecret,
ClientID: ev.GitlabClientId,
ClientSecret: ev.GitlabClientSecret,
Endpoint: oauthGitlab.Endpoint,
RedirectURL: callbackUrl,
Scopes: strings.Split(env.GitlabScopes, ","),
RedirectURL: ev.GitlabCallbackUrl,
Scopes: strings.Split(ev.GitlabScopes, ","),
}

return &gitlabI{cfg: &cfg}
return &gitlabI{enabled: true, cfg: &cfg}
}
Loading