github.com/google/go-safeweb@v0.0.0-20231219055052-64d8cfc90fbb/examples/sample-application/secure/auth/auth.go (about)

     1  // Copyright 2020 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //	https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //go:build go1.16
    16  // +build go1.16
    17  
    18  package auth
    19  
    20  import (
    21  	"github.com/google/go-safeweb/safehttp"
    22  	"github.com/google/safehtml/template"
    23  
    24  	"github.com/google/go-safeweb/examples/sample-application/secure/responses"
    25  	"github.com/google/go-safeweb/examples/sample-application/storage"
    26  )
    27  
    28  const sessionCookie = "SESSION"
    29  
    30  var unauthMsg = template.MustParseAndExecuteToHTML(`Please <a href="/">login</a> before visiting this page.`)
    31  
    32  // Interceptor is an auth (access control) interceptor.
    33  //
    34  // It showcases how safehttp.Interceptor could be implement to provide custom
    35  // security features. See https://pkg.go.dev/github.com/google/go-safeweb/safehttp#hdr-Interceptors.
    36  //
    37  // In order to interact with the interceptor, use functions from this package.
    38  // E.g. to clear a user session call ClearSession.
    39  type Interceptor struct {
    40  	DB *storage.DB
    41  }
    42  
    43  // Before runs before the request is passed to the handler.
    44  //
    45  // Implementation details: this interceptor uses IncomingRequest's context to
    46  // store user information that's read from a cookie.
    47  func (ip Interceptor) Before(w safehttp.ResponseWriter, r *safehttp.IncomingRequest, cfg safehttp.InterceptorConfig) safehttp.Result {
    48  	// Identify the user.
    49  	user := ip.userFromCookie(r)
    50  	if user != "" {
    51  		putUser(r.Context(), user)
    52  	}
    53  
    54  	if _, ok := cfg.(Skip); ok {
    55  		// If the config says we should not perform auth, let's stop executing here.
    56  		return safehttp.NotWritten()
    57  	}
    58  
    59  	if user == "" {
    60  		// We have to perform auth, and the user was not identified, bail out.
    61  		return w.WriteError(responses.Error{
    62  			StatusCode: safehttp.StatusUnauthorized,
    63  			Message:    unauthMsg,
    64  		})
    65  	}
    66  	return safehttp.NotWritten()
    67  }
    68  
    69  // Commit runs after the handler commited to a response.
    70  //
    71  // Implementation details: the interceptor reads IncomingRequest's context to
    72  // retrieve information about the user and to do what the handler asked it to
    73  // (through ClearSession or CreateSession).
    74  func (ip Interceptor) Commit(w safehttp.ResponseHeadersWriter, r *safehttp.IncomingRequest, resp safehttp.Response, cfg safehttp.InterceptorConfig) {
    75  	user := User(r)
    76  
    77  	switch ctxSessionAction(r.Context()) {
    78  	case clearSess:
    79  		ip.DB.DelSession(user)
    80  		w.AddCookie(safehttp.NewCookie(sessionCookie, ""))
    81  	case setSess:
    82  		token := ip.DB.GetToken(user)
    83  		w.AddCookie(safehttp.NewCookie(sessionCookie, token))
    84  	default:
    85  		// do nothing
    86  	}
    87  }
    88  
    89  func (Interceptor) Match(cfg safehttp.InterceptorConfig) bool {
    90  	_, ok := cfg.(Skip)
    91  	return ok
    92  }
    93  
    94  // User retrieves the user.
    95  func User(r *safehttp.IncomingRequest) string {
    96  	return ctxUser(r.Context())
    97  }
    98  
    99  func (ip Interceptor) userFromCookie(r *safehttp.IncomingRequest) string {
   100  	sess, err := r.Cookie(sessionCookie)
   101  	if err != nil || sess.Value() == "" {
   102  		return ""
   103  	}
   104  	user, ok := ip.DB.GetUser(sess.Value())
   105  	if !ok {
   106  		return ""
   107  	}
   108  	return user
   109  }
   110  
   111  // ClearSession clears the session.
   112  //
   113  // Implementation details: to interact with the interceptor, passes data through
   114  // the IncomingRequest's context.
   115  func ClearSession(r *safehttp.IncomingRequest) {
   116  	putSessionAction(r.Context(), clearSess)
   117  }
   118  
   119  // CreateSession creates a session.
   120  //
   121  // Implementation details: to interact with the interceptor, passes data through
   122  // the IncomingRequest's context.
   123  func CreateSession(r *safehttp.IncomingRequest, user string) {
   124  	putSessionAction(r.Context(), setSess)
   125  	putUser(r.Context(), user)
   126  }
   127  
   128  // Skip allows to mark an endpoint to skip auth checks.
   129  //
   130  // Its uses would normally be gated by a security review. You can use the
   131  // https://github.com/google/go-safeweb/blob/master/cmd/bancheck tool to enforce
   132  // this.
   133  type Skip struct{}