File size: 4,823 Bytes
979bf48
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
// This file should never be bundled into application's runtime code and should
// stay in the Next.js server.
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, "generateEncryptionKeyBase64", {
    enumerable: true,
    get: function() {
        return generateEncryptionKeyBase64;
    }
});
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
const _fs = /*#__PURE__*/ _interop_require_default(require("fs"));
const _cachedir = require("../cache-dir");
const _encryptionutils = require("./encryption-utils");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
// Keep the key in memory as it should never change during the lifetime of the server in
// both development and production.
let __next_encryption_key_generation_promise = null;
const CONFIG_FILE = '.rscinfo';
const ENCRYPTION_KEY = 'encryption.key';
const ENCRYPTION_EXPIRE_AT = 'encryption.expire_at';
const EXPIRATION = 1000 * 60 * 60 * 24 * 14 // 14 days
;
async function writeCache(distDir, configValue) {
    const cacheBaseDir = (0, _cachedir.getStorageDirectory)(distDir);
    if (!cacheBaseDir) return;
    const configPath = _path.default.join(cacheBaseDir, CONFIG_FILE);
    if (!_fs.default.existsSync(cacheBaseDir)) {
        await _fs.default.promises.mkdir(cacheBaseDir, {
            recursive: true
        });
    }
    await _fs.default.promises.writeFile(configPath, JSON.stringify({
        [ENCRYPTION_KEY]: configValue,
        [ENCRYPTION_EXPIRE_AT]: Date.now() + EXPIRATION
    }));
}
// This utility is used to get a key for the cache directory. If the
// key is not present, it will generate a new one and store it in the
// cache directory inside dist.
// The key will also expire after a certain amount of time. Once it
// expires, a new one will be generated.
// During the lifetime of the server, it will be reused and never refreshed.
async function loadOrGenerateKey(distDir, isBuild, generateKey) {
    const cacheBaseDir = (0, _cachedir.getStorageDirectory)(distDir);
    if (!cacheBaseDir) {
        // There's no persistent storage available. We generate a new key.
        // This also covers development time.
        return await generateKey();
    }
    const configPath = _path.default.join(cacheBaseDir, CONFIG_FILE);
    async function hasCachedKey() {
        if (!_fs.default.existsSync(configPath)) return false;
        try {
            const config = JSON.parse(await _fs.default.promises.readFile(configPath, 'utf8'));
            if (!config) return false;
            if (typeof config[ENCRYPTION_KEY] !== 'string' || typeof config[ENCRYPTION_EXPIRE_AT] !== 'number') {
                return false;
            }
            // For build time, we need to rotate the key if it's expired. Otherwise
            // (next start) we have to keep the key as it is so the runtime key matches
            // the build time key.
            if (isBuild && config[ENCRYPTION_EXPIRE_AT] < Date.now()) {
                return false;
            }
            const cachedKey = config[ENCRYPTION_KEY];
            // If encryption key is provided via env, and it's not same as valid cache,
            //  we should not use the cached key and respect the env key.
            if (cachedKey && process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY && cachedKey !== process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY) {
                return false;
            }
            return cachedKey;
        } catch  {
            // Broken config file. We should generate a new key and overwrite it.
            return false;
        }
    }
    const maybeValidKey = await hasCachedKey();
    if (typeof maybeValidKey === 'string') {
        return maybeValidKey;
    }
    const key = await generateKey();
    await writeCache(distDir, key);
    return key;
}
async function generateEncryptionKeyBase64({ isBuild, distDir }) {
    // This avoids it being generated multiple times in parallel.
    if (!__next_encryption_key_generation_promise) {
        __next_encryption_key_generation_promise = loadOrGenerateKey(distDir, isBuild, async ()=>{
            const providedKey = process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY;
            if (providedKey) {
                return providedKey;
            }
            const key = await crypto.subtle.generateKey({
                name: 'AES-GCM',
                length: 256
            }, true, [
                'encrypt',
                'decrypt'
            ]);
            const exported = await crypto.subtle.exportKey('raw', key);
            return btoa((0, _encryptionutils.arrayBufferToString)(exported));
        });
    }
    return __next_encryption_key_generation_promise;
}

//# sourceMappingURL=encryption-utils-server.js.map