blob: 74da5e6f06e8f28b97ef11f05c4bbac61df30edc [file] [log] [blame]
// Copyright 2019 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
"context"
"fmt"
"net/http"
"os"
"time"
"github.com/gomodule/redigo/redis"
"go.opentelemetry.io/otel"
"google.golang.org/protobuf/types/known/emptypb"
"go.chromium.org/luci/auth/identity"
"go.chromium.org/luci/common/logging"
"go.chromium.org/luci/server"
"go.chromium.org/luci/server/analytics"
"go.chromium.org/luci/server/auth"
"go.chromium.org/luci/server/auth/openid"
"go.chromium.org/luci/server/cron"
"go.chromium.org/luci/server/encryptedcookies"
"go.chromium.org/luci/server/gaeemulation"
"go.chromium.org/luci/server/loginsessions"
"go.chromium.org/luci/server/mailer"
"go.chromium.org/luci/server/module"
"go.chromium.org/luci/server/redisconn"
"go.chromium.org/luci/server/router"
"go.chromium.org/luci/server/secrets"
"go.chromium.org/luci/server/templates"
"go.chromium.org/luci/server/tq"
"go.chromium.org/luci/examples/k8s/helloworld/apipb"
// Use datastore as a backend for auth session and TQ transactions.
_ "go.chromium.org/luci/server/encryptedcookies/session/datastore"
_ "go.chromium.org/luci/server/tq/txn/datastore"
)
var tracer = otel.Tracer("go.chromium.org/luci/example")
func main() {
// Additional modules that extend the server functionality.
modules := []module.Module{
analytics.NewModuleFromFlags(),
cron.NewModuleFromFlags(),
encryptedcookies.NewModuleFromFlags(),
gaeemulation.NewModuleFromFlags(),
loginsessions.NewModuleFromFlags(),
mailer.NewModuleFromFlags(),
redisconn.NewModuleFromFlags(),
secrets.NewModuleFromFlags(),
tq.NewModuleFromFlags(),
}
server.Main(nil, modules, func(srv *server.Server) error {
// When running locally, serve static files ourself.
if !srv.Options.Prod {
srv.Routes.Static("/static", nil, http.Dir("./static"))
}
// gRPC example.
apipb.RegisterGreeterServer(srv, &greeterServer{})
// Logging and tracing example.
srv.Routes.GET("/log", nil, func(c *router.Context) {
logging.Debugf(c.Request.Context(), "Hello debug world")
ctx, span := tracer.Start(c.Request.Context(), "Testing")
logging.Infof(ctx, "Hello info world")
time.Sleep(100 * time.Millisecond)
span.End()
logging.Warningf(c.Request.Context(), "Hello warning world")
c.Writer.Write([]byte("Hello, world"))
logging.WithError(fmt.Errorf("boom")).Errorf(c.Request.Context(), "Hello error world")
})
// Redis example.
//
// To run Redis for tests locally (in particular on OSX):
// docker run --name redis -p 6379:6379 --restart always --detach redis
//
// Then launch the example with "... -redis-addr :6379".
//
// Note that it makes Redis port available on 0.0.0.0. This is a necessity
// when using Docker-for-Mac. Don't put any sensitive stuff there (or make
// sure your firewall is configured to block external connections).
srv.Routes.GET("/redis", nil, func(c *router.Context) {
conn, err := redisconn.Get(c.Request.Context())
if err != nil {
http.Error(c.Writer, err.Error(), 500)
return
}
defer conn.Close()
n, err := redis.Int(conn.Do("INCR", "testKey"))
if err != nil {
http.Error(c.Writer, err.Error(), 500)
return
}
fmt.Fprintf(c.Writer, "%d", n)
})
// OpenID token checks (e.g. for PubSub authenticated push subscription).
openIDCheck := auth.Authenticator{
Methods: []auth.Method{
&openid.GoogleIDTokenAuthMethod{
AudienceCheck: openid.AudienceMatchesHost,
},
},
}
mw := router.NewMiddlewareChain(openIDCheck.GetMiddleware())
srv.Routes.POST("/push", mw, func(c *router.Context) {
logging.Infof(c.Request.Context(), "Authenticated as %s", auth.CurrentIdentity(c.Request.Context()))
// TODO: check auth.CurrentIdentity(...) against an allowlist of allowed
// callers, etc.
})
// Using ID tokens for authenticating outbound calls. This synthetic example
// works on localhost only.
srv.Routes.GET("/call", mw, func(c *router.Context) {
tr, err := auth.GetRPCTransport(c.Request.Context(),
auth.AsSelf,
auth.WithIDTokenAudience("https://${host}"),
)
if err != nil {
http.Error(c.Writer, err.Error(), 500)
return
}
req, _ := http.NewRequest("POST", "http://127.0.0.1:8800/push", nil)
req.Host = "example.com"
resp, err := (&http.Client{Transport: tr}).Do(req)
if err != nil {
http.Error(c.Writer, err.Error(), 500)
return
}
defer resp.Body.Close()
})
// An example of a site that uses encrypted cookies for authentication.
templatesBundle := &templates.Bundle{
Loader: templates.FileSystemLoader(os.DirFS("templates")),
DebugMode: func(context.Context) bool { return !srv.Options.Prod },
DefaultArgs: func(ctx context.Context, e *templates.Extra) (templates.Args, error) {
loginURL, err := auth.LoginURL(ctx, e.Request.URL.RequestURI())
if err != nil {
return nil, err
}
logoutURL, err := auth.LogoutURL(ctx, e.Request.URL.RequestURI())
if err != nil {
return nil, err
}
return templates.Args{
"IsAnonymous": auth.CurrentIdentity(ctx) == identity.AnonymousIdentity,
"User": auth.CurrentUser(ctx),
"LoginURL": loginURL,
"LogoutURL": logoutURL,
"GoogleAnalyticsSnippet": analytics.Snippet(ctx),
}, nil
},
}
htmlPageMW := router.NewMiddlewareChain(
templates.WithTemplates(templatesBundle),
auth.Authenticate(srv.CookieAuth),
)
srv.Routes.GET("/", htmlPageMW, func(c *router.Context) {
templates.MustRender(c.Request.Context(), c.Writer, "pages/index.html", nil)
})
// To test redirects after login.
srv.Routes.GET("/test/*something", htmlPageMW, func(c *router.Context) {
templates.MustRender(c.Request.Context(), c.Writer, "pages/index.html", nil)
})
// Example of sending emails.
srv.Routes.GET("/send-mail", nil, func(c *router.Context) {
err := mailer.Send(c.Request.Context(), &mailer.Mail{
To: []string{"someone@example.com"},
Subject: "Hi",
TextBody: "How are you doing?",
})
if err != nil {
http.Error(c.Writer, err.Error(), 500)
}
})
return nil
})
}
type greeterServer struct{}
func (*greeterServer) SayHi(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) {
logging.Infof(ctx, "Hi")
time.Sleep(100 * time.Millisecond)
return &emptypb.Empty{}, nil
}