001/*
002 * SPDX-License-Identifier: Apache-2.0
003 *
004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors
005 *
006 * Licensed under the Apache License, Version 2.0 (the "License");
007 * you may not use this file except in compliance with the License.
008 * You may obtain a copy of the License at
009 *
010 *     https://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package dev.enola.common.secret;
019
020import java.io.IOException;
021import java.util.Optional;
022
023/**
024 * SecretManager is a "vault" of 🔑 {@link Secret}s.
025 *
026 * <p>You {@link #store(String, char[])}, then {@link #getOptional(String)} it; and maybe later
027 * {@link #delete(String)} it again. There is very intentionally no <code>Set&lt;String&gt;
028 * listKeys()</code> sort of method here.
029 */
030public interface SecretManager {
031
032    // TODO: Implement a FileSecretManager which stores the secrets in an encrypted file vault which
033    //   is decrypted with a passphrase which the user must type when starting an application is a
034    //   possibility.  We, however, prefer focusing on implementations based on OS support.
035
036    // TODO Write an ExecAgeSecretManager for https://github.com/Foxboron/age-plugin-tpm
037    //   by generalizing the existing initial ExecPassSecretManager
038
039    // TODO Write an AppleSecretManager
040    //   Initially using https://github.com/remko/age-plugin-se
041    //   Later see if there is some direct Java JCA for HSM API for this?
042
043    // TODO Write a WindowsSecretManager
044
045    // TODO Write CLI with ./enola secret store
046
047    // TODO Write a multi-user dev.enola.common.context.TLC-based wrapper for this.
048
049    /**
050     * Stores a secret value associated with a unique key. The sensitive value is provided as a char
051     * array. Implementations will zero out the input {@code value} array immediately after calling
052     * this method for security. If a secret with the same key already exists, its value is
053     * overwritten.
054     *
055     * @param key The unique key (name) for the secret.
056     * @param value The sensitive secret value as a character array.
057     * @throws IOException If an error occurs while storing the secret.
058     */
059    void store(String key, char[] value) throws IOException;
060
061    /**
062     * Retrieves a secret by its key, with optionality.
063     *
064     * @param key The unique key (name) of the secret to retrieve.
065     * @return An {@link Optional} containing the {@link Secret}, if found.
066     * @throws IOException If an error occurs while retrieving the secret.
067     */
068    Optional<Secret> getOptional(String key) throws IOException;
069
070    /**
071     * Retrieves a secret by its key; throws if not found.
072     *
073     * @param key The unique key (name) of the secret to retrieve.
074     * @return An {@link Optional} containing the {@link Secret}, if found.
075     * @throws IllegalStateException if the secret is not found.
076     * @throws IOException If an error occurs while retrieving the secret.
077     */
078    default Secret get(String key) throws IllegalStateException, IOException {
079        return getOptional(key)
080                .orElseThrow(
081                        () ->
082                                new IllegalStateException(
083                                        getClass().getSimpleName() + " missing secret: " + key));
084    }
085
086    /**
087     * Deletes a secret from the manager.
088     *
089     * @param key The unique key (name) of the secret to delete.
090     * @throws IOException If an error occurs while deleting the secret.
091     */
092    void delete(String key) throws IOException;
093}