• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

node-opcua / node-opcua / 23974043205

04 Apr 2026 07:17AM UTC coverage: 92.589% (+0.01%) from 92.576%
23974043205

push

github

erossignon
chore: fix Mocha.Suite.settimeout misused

18408 of 21832 branches covered (84.32%)

161708 of 174651 relevant lines covered (92.59%)

461089.77 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.99
/packages/node-opcua-secure-channel/source/token_stack.ts
1
import chalk from "chalk";
1✔
2
import { assert } from "node-opcua-assert";
1✔
3
import type { DateTime } from "node-opcua-basic-types";
1✔
4
import type { DerivedKeys } from "node-opcua-crypto/web";
1✔
5
import { checkDebugFlag, make_debugLog, make_warningLog } from "node-opcua-debug";
1✔
6
import type { DerivedKeys1 } from "./security_policy";
1✔
7

1✔
8
const debugLog = make_debugLog("TOKEN");
1✔
9
const doDebug = checkDebugFlag("TOKEN");
1✔
10
const _warningLog = make_warningLog("TOKEN");
1✔
11

1✔
12
export interface ISecurityToken {
1✔
13
    tokenId: number;
1✔
14
    createdAt: DateTime;
1✔
15
    revisedLifetime: number;
1✔
16
    channelId: number;
1✔
17
}
1✔
18

1✔
19
export interface SecurityTokenAndDerivedKeys {
1✔
20
    securityToken: ISecurityToken;
1✔
21
    // derivedKeys might not be defined if security mode is none
1✔
22
    derivedKeys: DerivedKeys1 | null;
1✔
23
}
1✔
24

1✔
25
function hasTokenReallyExpired(token: ISecurityToken): boolean {
12,712✔
26
    const now = new Date();
12,712✔
27
    const age = now.getTime() - (token.createdAt?.getTime() ?? 0);
12,712!
28
    return age > token.revisedLifetime * 1.25;
12,712✔
29
}
12,712✔
30

1✔
31
export interface IDerivedKeyProvider {
1✔
32
    getDerivedKey(tokenId: number): DerivedKeys | null;
1✔
33
}
1✔
34
export class TokenStack {
3,060✔
35
    #tokenStack: SecurityTokenAndDerivedKeys[] = [];
3,060✔
36

3,060✔
37
    #clientKeyProvider: IDerivedKeyProvider;
3,060✔
38
    #serverKeyProvider: IDerivedKeyProvider;
3,060✔
39

3,060✔
40
    private id: number = 0;
3,060✔
41
    constructor(channelId: number) {
3,060✔
42
        this.id = channelId;
3,060✔
43
        this.#clientKeyProvider = {
3,060✔
44
            getDerivedKey: (tokenId: number): DerivedKeys | null => {
3,060✔
45
                const d = this.getTokenDerivedKeys(tokenId);
6,394✔
46
                if (!d) return null;
6,394✔
47
                return d.derivedClientKeys;
6,392✔
48
            }
6,392✔
49
        };
3,060✔
50
        this.#serverKeyProvider = {
3,060✔
51
            getDerivedKey: (tokenId: number): DerivedKeys | null => {
3,060✔
52
                const d = this.getTokenDerivedKeys(tokenId);
2,797✔
53
                if (!d) return null;
2,797!
54
                return d.derivedServerKeys;
2,797✔
55
            }
2,797✔
56
        };
3,060✔
57
    }
3,060✔
58
    public serverKeyProvider(): IDerivedKeyProvider {
3,060✔
59
        return this.#serverKeyProvider;
1,515✔
60
    }
1,515✔
61
    public clientKeyProvider(): IDerivedKeyProvider {
3,060✔
62
        return this.#clientKeyProvider;
4,695✔
63
    }
4,695✔
64
    public pushNewToken(securityToken: ISecurityToken, derivedKeys: DerivedKeys1 | null): void {
3,060✔
65
        this.removeOldTokens();
3,388✔
66

3,388✔
67
        // TODO: make sure this list doesn't grow indefinitely
3,388✔
68
        const _tokenStack = this.#tokenStack;
3,388✔
69
        assert(_tokenStack.length === 0 || _tokenStack[0].securityToken.tokenId !== securityToken.tokenId);
3,388✔
70
        _tokenStack.push({
3,388✔
71
            derivedKeys,
3,388✔
72
            securityToken
3,388✔
73
        });
3,388✔
74
        /* c8 ignore next */
1✔
75
        if (doDebug) {
1✔
76
            debugLog("id=", this.id, chalk.cyan("Pushing new token with id "), securityToken.tokenId, this.tokenIds());
×
77
        }
×
78
    }
3,388✔
79
    private tokenIds() {
3,060✔
80
        return this.#tokenStack.map((a) => a.securityToken.tokenId);
×
81
    }
×
82
    public getToken(tokenId: number): ISecurityToken | null {
3,060✔
83
        const token = this.#tokenStack.find((a) => a.securityToken.tokenId === tokenId);
46,464✔
84
        if (!token) return null;
46,464!
85
        return token.securityToken;
46,464✔
86
    }
46,464✔
87
    public getTokenDerivedKeys(tokenId: number): DerivedKeys1 | null {
3,060✔
88
        const token = this.#tokenStack.find((a) => a.securityToken.tokenId === tokenId);
11,983✔
89
        if (!token) return null;
11,983!
90

11,983✔
91
        if (hasTokenReallyExpired(token.securityToken)) {
11,983✔
92
            return null;
2✔
93
        }
2✔
94
        return token.derivedKeys;
11,981✔
95
    }
11,981✔
96

3,060✔
97
    /**
3,060✔
98
     * Returns the derived keys for the most recent (newest) token
3,060✔
99
     * that has not "really expired".
3,060✔
100
     *
3,060✔
101
     * This is used as a fallback when the tokenId from the original
3,060✔
102
     * request has expired: as per OPC UA Part 6 §6.7.3, the server
3,060✔
103
     * should use the current active token to secure outgoing messages.
3,060✔
104
     */
3,060✔
105
    public getLatestTokenDerivedKeys(): { tokenId: number; derivedKeys: DerivedKeys1 } | null {
3,060✔
106
        // Walk backwards — newest tokens are at the end
×
107
        for (let i = this.#tokenStack.length - 1; i >= 0; i--) {
×
108
            const entry = this.#tokenStack[i];
×
109
            if (!hasTokenReallyExpired(entry.securityToken) && entry.derivedKeys) {
×
110
                return {
×
111
                    tokenId: entry.securityToken.tokenId,
×
112
                    derivedKeys: entry.derivedKeys
×
113
                };
×
114
            }
×
115
        }
×
116
        return null;
×
117
    }
×
118

3,060✔
119
    public removeOldTokens() {
3,060✔
120
        // remove all expired tokens
3,388✔
121
        this.#tokenStack = this.#tokenStack.filter((token) => !hasTokenReallyExpired(token.securityToken));
3,388✔
122
    }
3,388✔
123
}
3,060✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc