~sircmpwn/gql.sr.ht

646e9d90c4b1c3c31e14807185afe0db3f5cfe89 — Drew DeVault 3 years ago 5206db0
Add OAuth client internal auth mechanism
1 files changed, 70 insertions(+), 3 deletions(-)

M auth/middleware.go
M auth/middleware.go => auth/middleware.go +70 -3
@@ 145,6 145,65 @@ func authForUsername(ctx context.Context, username string) (*AuthContext, error)
	return &auth, nil
}

func authForOAuthClient(ctx context.Context, clientUUID string) (*AuthContext, error) {
	var auth AuthContext
	if err := database.WithTx(ctx, &sql.TxOptions{
		Isolation: 0,
		ReadOnly: true,
	}, func(tx *sql.Tx) error {
		var (
			err  error
			rows *sql.Rows
		)
		query := database.
			Select(ctx, []string{
				`u.id`, `u.username`,
				`u.created`, `u.updated`,
				`u.email`,
				`u.user_type`,
				`u.url`, `u.location`, `u.bio`,
				`u.suspension_notice`,
			}).
			From(`"oauth2_client" client`).
			Join(`"user" u ON u.id = client.owner_id`).
			Where(`client.client_uuid = ?`, clientUUID)
		if rows, err = query.RunWith(tx).Query(); err != nil {
			panic(err)
		}
		defer rows.Close()

		if !rows.Next() {
			if err := rows.Err(); err != nil {
				panic(err)
			}
			return fmt.Errorf("Authenticating for unknown client ID %s", clientUUID)
		}
		if err := rows.Scan(&auth.UserID, &auth.Username, &auth.Created,
			&auth.Updated, &auth.Email, &auth.UserType, &auth.URL, &auth.Location,
			&auth.Bio, &auth.SuspensionNotice); err != nil {
			panic(err)
		}
		if rows.Next() {
			// TODO: Fetch user info from meta if necessary
			if err := rows.Err(); err != nil {
				panic(err)
			}
			panic(errors.New("Multiple matching user accounts; invariant broken"))
		}
		return nil
	}); err != nil {
		return nil, err
	}

	if auth.UserType == USER_SUSPENDED {
		return nil, fmt.Errorf(
			"Account suspended with the following notice: %s\nContact support",
			auth.SuspensionNotice)
	}

	return &auth, nil
}

type AuthCookie struct {
	// The username of the authenticated user
	Name string `json:"name"`


@@ 185,6 244,9 @@ type InternalAuth struct {

	// An arbitrary identifier for this internal node, e.g. "us-east-3.git.sr.ht"
	NodeID string `json:"node_id"`

	// Only used by specific meta.sr.ht routes
	OAuthClientUUID string `json:"oauth_client_id",omit-empty`
}

func internalAuth(internalNet []*net.IPNet, payload []byte,


@@ 224,7 286,12 @@ func internalAuth(internalNet []*net.IPNet, payload []byte,
		authError(w, "Invalid Authorization header", http.StatusForbidden)
	}

	auth, err := authForUsername(r.Context(), internalAuth.Name)
	var auth *AuthContext
	if internalAuth.OAuthClientUUID != "" {
		auth, err = authForOAuthClient(r.Context(), internalAuth.OAuthClientUUID)
	} else {
		auth, err = authForUsername(r.Context(), internalAuth.Name)
	}
	if err != nil {
		authError(w, err.Error(), http.StatusForbidden)
		return


@@ 472,7 539,7 @@ func OAuth2(token string, hash [64]byte, w http.ResponseWriter,

	if ot.Grants != "" {
		auth.Access = make(map[string]string)
		for _, grant := range strings.Split(ot.Grants, ",") {
		for _, grant := range strings.Split(ot.Grants, " ") {
			var (
				service string
				scope   string


@@ 480,7 547,7 @@ func OAuth2(token string, hash [64]byte, w http.ResponseWriter,
			)
			parts := strings.Split(grant, "/")
			if len(parts) != 2 {
				panic(errors.New("OAuth grant without service/scope format"))
				panic(fmt.Errorf("OAuth grant '%s' without service/scope format", grant))
			}
			service = parts[0]
			parts = strings.Split(parts[1], ":")