// Copyright 2021 Google LLC
//
// 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.
//
///////////////////////////////////////////////////////////////////////////////

#include "tink/jwt/raw_jwt.h"

#include <string>
#include <utility>
#include <vector>

#include "absl/status/status.h"
#include "absl/strings/numbers.h"
#include "absl/strings/str_format.h"
#include "absl/strings/substitute.h"
#include "absl/time/time.h"
#include "tink/jwt/internal/json_util.h"

namespace crypto {
namespace tink {

namespace {

using ::google::protobuf::Struct;
using ::google::protobuf::Value;

// Registered claim names, as defined in
// https://tools.ietf.org/html/rfc7519#section-4.1.
constexpr absl::string_view kJwtClaimIssuer = "iss";
constexpr absl::string_view kJwtClaimSubject = "sub";
constexpr absl::string_view kJwtClaimAudience = "aud";
constexpr absl::string_view kJwtClaimExpiration = "exp";
constexpr absl::string_view kJwtClaimNotBefore = "nbf";
constexpr absl::string_view kJwtClaimIssuedAt = "iat";
constexpr absl::string_view kJwtClaimJwtId = "jti";

constexpr int64_t kJwtTimestampMax = 253402300799;  // 31 Dec 9999, 23:59:59 GMT

bool IsRegisteredClaimName(absl::string_view name) {
  return name == kJwtClaimIssuer || name == kJwtClaimSubject ||
         name == kJwtClaimAudience || name == kJwtClaimExpiration ||
         name == kJwtClaimNotBefore || name == kJwtClaimIssuedAt ||
         name == kJwtClaimJwtId;
}

util::Status ValidatePayloadName(absl::string_view name) {
  if (IsRegisteredClaimName(name)) {
    return absl::InvalidArgumentError(absl::Substitute(
        "claim '$0' is invalid because it's a registered name; "
        "use the corresponding getter or setter method.",
        name));
  }
  return util::OkStatus();
}

bool HasClaimOfKind(const google::protobuf::Struct& json_proto,
                    absl::string_view name, Value::KindCase kind) {
  if (IsRegisteredClaimName(name)) {
    return false;
  }
  const auto& fields = json_proto.fields();
  auto it = fields.find(std::string(name));
  if (it == fields.end()) {
    return false;
  }
  const Value& value = it->second;
  return value.kind_case() == kind;
}

// Returns true if the claim is present but not a string.
bool ClaimIsNotAString(const google::protobuf::Struct& json_proto,
                       absl::string_view name) {
  const auto& fields = json_proto.fields();
  auto it = fields.find(std::string(name));
  if (it == fields.end()) {
    return false;
  }
  const Value& value = it->second;
  return value.kind_case() != Value::kStringValue;
}

// Returns true if the claim is present but not a list.
bool ClaimIsNotAList(google::protobuf::Struct& json_proto,
                     absl::string_view name) {
  const auto& fields = json_proto.fields();
  auto it = fields.find(std::string(name));
  if (it == fields.end()) {
    return false;
  }
  const Value& value = it->second;
  return value.kind_case() != Value::kListValue;
}

// Returns true if the claim is present but not a timestamp.
bool ClaimIsNotATimestamp(const google::protobuf::Struct& json_proto,
                          absl::string_view name) {
  const auto& fields = json_proto.fields();
  auto it = fields.find(std::string(name));
  if (it == fields.end()) {
    return false;
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kNumberValue) {
    return true;
  }
  double timestamp = value.number_value();
  return (timestamp > kJwtTimestampMax) || (timestamp < 0);
}

int64_t TimeToTimestamp(absl::Time time) {
  // We round the timestamp to a whole number. We always round down.
  return absl::ToUnixSeconds(time);
}

absl::Time TimestampToTime(double timestamp) {
  if (timestamp > kJwtTimestampMax) {
    return absl::FromUnixSeconds(kJwtTimestampMax);
  }
  return absl::FromUnixSeconds(timestamp);
}

util::Status ValidateAudienceClaim(const google::protobuf::Struct& json_proto) {
  const auto& fields = json_proto.fields();
  auto it = fields.find(std::string(kJwtClaimAudience));
  if (it == fields.end()) {
    return util::OkStatus();
  }
  const Value& value = it->second;
  if (value.kind_case() == Value::kStringValue) {
    return util::OkStatus();
  }
  if (value.kind_case() != Value::kListValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "aud claim is not a list");
  }
  if (value.list_value().values_size() < 1) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "aud claim is present but empty");
  }
  for (const Value& v : value.list_value().values()) {
    if (v.kind_case() != Value::kStringValue) {
      return util::Status(absl::StatusCode::kInvalidArgument,
                          "aud claim is not a list of strings");
    }
  }
  return util::OkStatus();
}

}  // namespace

util::StatusOr<RawJwt> RawJwt::FromJson(absl::optional<std::string> type_header,
                                        absl::string_view json_payload) {
  util::StatusOr<google::protobuf::Struct> proto =
      jwt_internal::JsonStringToProtoStruct(json_payload);
  if (!proto.ok()) {
    return proto.status();
  }
  if (ClaimIsNotAString(*proto, kJwtClaimIssuer) ||
      ClaimIsNotAString(*proto, kJwtClaimSubject) ||
      ClaimIsNotATimestamp(*proto, kJwtClaimExpiration) ||
      ClaimIsNotATimestamp(*proto, kJwtClaimNotBefore) ||
      ClaimIsNotATimestamp(*proto, kJwtClaimIssuedAt)) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "contains an invalid registered claim");
  }
  util::Status aud_status = ValidateAudienceClaim(*proto);
  if (!aud_status.ok()) {
    return aud_status;
  }
  RawJwt token(type_header, *std::move(proto));
  return token;
}

util::StatusOr<std::string> RawJwt::GetJsonPayload() const {
  return jwt_internal::ProtoStructToJsonString(json_proto_);
}

RawJwt::RawJwt() = default;

RawJwt::RawJwt(absl::optional<std::string> type_header,
               google::protobuf::Struct json_proto) {
  type_header_ = type_header;
  json_proto_ = json_proto;
}

bool RawJwt::HasTypeHeader() const { return type_header_.has_value(); }

util::StatusOr<std::string> RawJwt::GetTypeHeader() const {
  if (!type_header_.has_value()) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "No type header found");
  }
  return *type_header_;
}

bool RawJwt::HasIssuer() const {
  return json_proto_.fields().contains(std::string(kJwtClaimIssuer));
}

util::StatusOr<std::string> RawJwt::GetIssuer() const {
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(kJwtClaimIssuer));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kInvalidArgument, "No Issuer found");
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kStringValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "Issuer is not a string");
  }
  return value.string_value();
}

bool RawJwt::HasSubject() const {
  return json_proto_.fields().contains(std::string(kJwtClaimSubject));
}

util::StatusOr<std::string> RawJwt::GetSubject() const {
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(kJwtClaimSubject));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kInvalidArgument, "No Subject found");
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kStringValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "Subject is not a string");
  }
  return value.string_value();
}

bool RawJwt::HasAudiences() const {
  return json_proto_.fields().contains(std::string(kJwtClaimAudience));
}

util::StatusOr<std::vector<std::string>> RawJwt::GetAudiences() const {
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(kJwtClaimAudience));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound, "No Audiences found");
  }
  Value list = it->second;
  if (list.kind_case() != Value::kListValue) {
    std::vector<std::string> audiences;
    audiences.push_back(list.string_value());
    return audiences;
  }
  if (list.kind_case() != Value::kListValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "Audiences is not a list");
  }
  std::vector<std::string> audiences;
  for (const auto& value : list.list_value().values()) {
    if (value.kind_case() != Value::kStringValue) {
      return util::Status(absl::StatusCode::kInvalidArgument,
                          "Audiences is not a list of strings");
    }
    audiences.push_back(value.string_value());
  }
  return audiences;
}

bool RawJwt::HasJwtId() const {
  return json_proto_.fields().contains(std::string(kJwtClaimJwtId));
}

util::StatusOr<std::string> RawJwt::GetJwtId() const {
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(kJwtClaimJwtId));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound, "No JwtId found");
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kStringValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "JwtId is not a string");
  }
  return value.string_value();
}

bool RawJwt::HasExpiration() const {
  return json_proto_.fields().contains(std::string(kJwtClaimExpiration));
}

util::StatusOr<absl::Time> RawJwt::GetExpiration() const {
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(kJwtClaimExpiration));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound, "No Expiration found");
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kNumberValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "Expiration is not a number");
  }
  return TimestampToTime(value.number_value());
}

bool RawJwt::HasNotBefore() const {
  return json_proto_.fields().contains(std::string(kJwtClaimNotBefore));
}

util::StatusOr<absl::Time> RawJwt::GetNotBefore() const {
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(kJwtClaimNotBefore));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound, "No NotBefore found");
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kNumberValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "NotBefore is not a number");
  }
  return TimestampToTime(value.number_value());
}

bool RawJwt::HasIssuedAt() const {
  return json_proto_.fields().contains(std::string(kJwtClaimIssuedAt));
}

util::StatusOr<absl::Time> RawJwt::GetIssuedAt() const {
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(kJwtClaimIssuedAt));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound, "No IssuedAt found");
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kNumberValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        "IssuedAt is not a number");
  }
  return TimestampToTime(value.number_value());
}

bool RawJwt::IsNullClaim(absl::string_view name) const {
  return HasClaimOfKind(json_proto_, name, Value::kNullValue);
}

bool RawJwt::HasBooleanClaim(absl::string_view name) const {
  return HasClaimOfKind(json_proto_, name, Value::kBoolValue);
}

util::StatusOr<bool> RawJwt::GetBooleanClaim(
    absl::string_view name) const {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    return status;
  }
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(name));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound,
                        absl::Substitute("claim '$0' not found", name));
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kBoolValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        absl::Substitute("claim '$0' is not a bool", name));
  }
  return value.bool_value();
}

bool RawJwt::HasStringClaim(absl::string_view name) const {
  return HasClaimOfKind(json_proto_, name, Value::kStringValue);
}

util::StatusOr<std::string> RawJwt::GetStringClaim(
    absl::string_view name) const {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    return status;
  }
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(name));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound,
                        absl::Substitute("claim '$0' not found", name));
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kStringValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        absl::Substitute("claim '$0' is not a string", name));
  }
  return value.string_value();
}

bool RawJwt::HasNumberClaim(absl::string_view name) const {
  return HasClaimOfKind(json_proto_, name, Value::kNumberValue);
}

util::StatusOr<double> RawJwt::GetNumberClaim(absl::string_view name) const {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    return status;
  }
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(name));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound,
                        absl::Substitute("claim '$0' not found", name));
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kNumberValue) {
    return util::Status(absl::StatusCode::kInvalidArgument,
                        absl::Substitute("claim '$0' is not a number", name));
  }
  return value.number_value();
}

bool RawJwt::HasJsonObjectClaim(absl::string_view name) const {
  return HasClaimOfKind(json_proto_, name, Value::kStructValue);
}

util::StatusOr<std::string> RawJwt::GetJsonObjectClaim(
    absl::string_view name) const {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    return status;
  }
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(name));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound,
                        absl::Substitute("claim '$0' not found", name));
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kStructValue) {
    return util::Status(
        absl::StatusCode::kInvalidArgument,
        absl::Substitute("claim '$0' is not a JSON object", name));
  }
  return jwt_internal::ProtoStructToJsonString(value.struct_value());
}

bool RawJwt::HasJsonArrayClaim(absl::string_view name) const {
  return HasClaimOfKind(json_proto_, name, Value::kListValue);
}

util::StatusOr<std::string> RawJwt::GetJsonArrayClaim(
    absl::string_view name) const {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    return status;
  }
  const auto& fields = json_proto_.fields();
  auto it = fields.find(std::string(name));
  if (it == fields.end()) {
    return util::Status(absl::StatusCode::kNotFound,
                        absl::Substitute("claim '$0' not found", name));
  }
  const Value& value = it->second;
  if (value.kind_case() != Value::kListValue) {
    return util::Status(
        absl::StatusCode::kInvalidArgument,
        absl::Substitute("claim '$0' is not a JSON array", name));
  }
  return jwt_internal::ProtoListToJsonString(value.list_value());
}

std::vector<std::string> RawJwt::CustomClaimNames() const {
  const auto& fields = json_proto_.fields();
  std::vector<std::string> values;
  for (auto it = fields.begin(); it != fields.end(); it++) {
    if (!IsRegisteredClaimName(it->first)) {
      values.push_back(it->first);
    }
  }
  return values;
}

RawJwtBuilder::RawJwtBuilder() { without_expiration_ = false; }

RawJwtBuilder& RawJwtBuilder::SetTypeHeader(absl::string_view type_header) {
  type_header_ = std::string(type_header);
  return *this;
}

RawJwtBuilder& RawJwtBuilder::SetIssuer(absl::string_view issuer) {
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_string_value(std::string(issuer));
  (*fields)[std::string(kJwtClaimIssuer)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::SetSubject(absl::string_view subject) {
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_string_value(std::string(subject));
  (*fields)[std::string(kJwtClaimSubject)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::SetAudience(absl::string_view audience) {
  // Make sure that "aud" is not already a list by a call to SetAudiences or
  // AddAudience.
  if (ClaimIsNotAString(json_proto_, kJwtClaimAudience)) {
    error_ = util::Status(absl::StatusCode::kInvalidArgument,
                          "SetAudience() must not be called together with "
                          "SetAudiences() or AddAudience");
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_string_value(std::string(audience));
  (*fields)[std::string(kJwtClaimAudience)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::SetAudiences(std::vector<std::string> audiences) {
  // Make sure that "aud" is not already a string by a call to SetAudience.
  if (ClaimIsNotAList(json_proto_, kJwtClaimAudience)) {
    error_ = util::Status(
        absl::StatusCode::kInvalidArgument,
        "SetAudiences() and SetAudience() must not be called together");
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  for (const auto& audience : audiences) {
    value.mutable_list_value()->add_values()->set_string_value(audience);
  }
  (*fields)[std::string(kJwtClaimAudience)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::AddAudience(absl::string_view audience) {
  // Make sure that "aud" is not already a string by a call to SetAudience.
  if (ClaimIsNotAList(json_proto_, kJwtClaimAudience)) {
    error_ = util::Status(
        absl::StatusCode::kInvalidArgument,
        "AddAudience() and SetAudience() must not be called together");
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  auto insertion_result =
      fields->insert({std::string(kJwtClaimAudience), Value()});
  google::protobuf::ListValue* list_value =
      insertion_result.first->second.mutable_list_value();
  list_value->add_values()->set_string_value(std::string(audience));
  return *this;
}

RawJwtBuilder& RawJwtBuilder::SetJwtId(absl::string_view jwid) {
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_string_value(std::string(jwid));
  (*fields)[std::string(kJwtClaimJwtId)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::WithoutExpiration() {
  without_expiration_ = true;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::SetExpiration(absl::Time expiration) {
  int64_t exp_timestamp = TimeToTimestamp(expiration);
  if ((exp_timestamp > kJwtTimestampMax) || (exp_timestamp < 0)) {
    if (!error_.has_value()) {
      error_ = util::Status(absl::StatusCode::kInvalidArgument,
                            "invalid expiration timestamp");
    }
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_number_value(exp_timestamp);
  (*fields)[std::string(kJwtClaimExpiration)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::SetNotBefore(absl::Time not_before) {
  int64_t nbf_timestamp = TimeToTimestamp(not_before);
  if ((nbf_timestamp > kJwtTimestampMax) || (nbf_timestamp < 0)) {
    if (!error_.has_value()) {
      error_ = util::Status(absl::StatusCode::kInvalidArgument,
                            "invalid not_before timestamp");
    }
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_number_value(nbf_timestamp);
  (*fields)[std::string(kJwtClaimNotBefore)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::SetIssuedAt(absl::Time issued_at) {
  int64_t iat_timestamp = TimeToTimestamp(issued_at);
  if ((iat_timestamp > kJwtTimestampMax) || (iat_timestamp < 0)) {
    if (!error_.has_value()) {
      error_ = util::Status(absl::StatusCode::kInvalidArgument,
                            "invalid issued_at timestamp");
    }
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_number_value(iat_timestamp);
  (*fields)[std::string(kJwtClaimIssuedAt)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::AddNullClaim(absl::string_view name) {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    if (!error_.has_value()) {
      error_ = status;
    }
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_null_value(google::protobuf::NULL_VALUE);
  (*fields)[std::string(name)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::AddBooleanClaim(absl::string_view name,
                                              bool bool_value) {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    if (!error_.has_value()) {
      error_ = status;
    }
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_bool_value(bool_value);
  (*fields)[std::string(name)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::AddStringClaim(absl::string_view name,
                                             absl::string_view string_value) {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    if (!error_.has_value()) {
      error_ = status;
    }
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_string_value(std::string(string_value));
  (*fields)[std::string(name)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::AddNumberClaim(absl::string_view name,
                                             double double_value) {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    if (!error_.has_value()) {
      error_ = status;
    }
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  value.set_number_value(double_value);
  (*fields)[std::string(name)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::AddJsonObjectClaim(
    absl::string_view name, absl::string_view object_value) {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    if (!error_.has_value()) {
      error_ = status;
    }
    return *this;
  }
  util::StatusOr<google::protobuf::Struct> proto =
      jwt_internal::JsonStringToProtoStruct(object_value);
  if (!proto.ok()) {
    if (!error_.has_value()) {
      error_ = proto.status();
    }
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  *value.mutable_struct_value() = *std::move(proto);
  (*fields)[std::string(name)] = value;
  return *this;
}

RawJwtBuilder& RawJwtBuilder::AddJsonArrayClaim(absl::string_view name,
                                                absl::string_view array_value) {
  util::Status status = ValidatePayloadName(name);
  if (!status.ok()) {
    if (!error_.has_value()) {
      error_ = status;
    }
    return *this;
  }
  util::StatusOr<google::protobuf::ListValue> list =
      jwt_internal::JsonStringToProtoList(array_value);
  if (!list.ok()) {
    if (!error_.has_value()) {
      error_ = list.status();
    }
    return *this;
  }
  auto fields = json_proto_.mutable_fields();
  Value value;
  *value.mutable_list_value() = *list;
  (*fields)[std::string(name)] = value;
  return *this;
}

util::StatusOr<RawJwt> RawJwtBuilder::Build() {
  if (error_.has_value()) {
    return *error_;
  }
  if (!json_proto_.fields().contains(std::string(kJwtClaimExpiration)) &&
      !without_expiration_) {
    return util::Status(
        absl::StatusCode::kInvalidArgument,
        "neither SetExpiration() nor WithoutExpiration() was called");
  }
  if (json_proto_.fields().contains(std::string(kJwtClaimExpiration)) &&
      without_expiration_) {
    return util::Status(
        absl::StatusCode::kInvalidArgument,
        "SetExpiration() and WithoutExpiration() must not be called together");
  }
  RawJwt token(type_header_, json_proto_);
  return token;
}

}  // namespace tink
}  // namespace crypto
