/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License;
 * you may not use this file except in compliance with the Elastic License.
 */
package org.elasticsearch.xpack.security.authc;

import org.elasticsearch.Version;
import org.elasticsearch.common.UUIDs;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Writeable;

import java.io.IOException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Objects;

/**
 * This token is a combination of a {@link Authentication} object with an expiry. This token can be
 * serialized for use later. Note, if serializing this token to a entity outside of the cluster,
 * care must be taken to encrypt and validate the serialized bytes or they cannot be trusted.
 *
 * Additionally, care must also be used when transporting these tokens as a stolen token can be
 * used by an adversary to gain access. For this reason, TLS must be enabled for these tokens to
 * be used.
 */
public final class UserToken implements Writeable {

    private final Version version;
    private final String id;
    private final Authentication authentication;
    private final ZonedDateTime expirationTime;

    /**
     * Create a new token with an autogenerated id
     */
    UserToken(Authentication authentication, ZonedDateTime expirationTime) {
        this.version = Version.CURRENT;
        this.id = UUIDs.base64UUID();
        this.authentication = Objects.requireNonNull(authentication);
        this.expirationTime = Objects.requireNonNull(expirationTime);
    }

    /**
     * Creates a new token based on the values from the stream
     */
    UserToken(StreamInput input) throws IOException {
        this.version = input.getVersion();
        this.id = input.readString();
        this.authentication = new Authentication(input);
        this.expirationTime = Instant.ofEpochSecond(input.readLong(), input.readInt())
                .atZone(ZoneId.of(input.readString()));
    }

    @Override
    public void writeTo(StreamOutput out) throws IOException {
        out.writeString(id);
        authentication.writeTo(out);
        out.writeLong(expirationTime.toEpochSecond());
        out.writeInt(expirationTime.getNano());
        out.writeString(expirationTime.getZone().getId());
    }

    /**
     * Get the authentication
     */
    Authentication getAuthentication() {
        return authentication;
    }

    /**
     * Get the expiration time
     */
    ZonedDateTime getExpirationTime() {
        return expirationTime;
    }

    /**
     * The ID of this token
     */
    String getId() {
        return id;
    }

    /**
     * The version of the node this token was created on
     */
    Version getVersion() {
        return version;
    }
}
