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

alkem-io / server / #7305

10 Jun 2024 12:01PM UTC coverage: 13.538%. First build
#7305

Pull #4075

travis-ci

Pull Request #4075: Patch - VC credentials

114 of 4337 branches covered (2.63%)

Branch coverage included in aggregate %.

8 of 48 new or added lines in 10 files covered. (16.67%)

1803 of 9823 relevant lines covered (18.35%)

2.75 hits per line

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

18.44
/src/core/authentication/authentication.service.ts
1
import { ConfigService } from '@nestjs/config';
1✔
2
import { Inject, Injectable, LoggerService } from '@nestjs/common';
1✔
3
import { Session } from '@ory/kratos-client';
1✔
4
import { LogContext } from '@common/enums';
5
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
6
import { AgentInfo } from '../authentication.agent.info/agent.info';
7
import { NotSupportedException } from '@common/exceptions';
8
import { AgentInfoCacheService } from '@core/authentication.agent.info/agent.info.cache.service';
9
import ConfigUtils from '@config/config.utils';
1✔
10
import { AlkemioConfig } from '@src/types';
1✔
11
import { KratosService } from '@services/infrastructure/kratos/kratos.service';
1✔
12
import { OryDefaultIdentitySchema } from '@services/infrastructure/kratos/types/ory.default.identity.schema';
1✔
13
import { AgentInfoService } from '@core/authentication.agent.info/agent.info.service';
14
import { AgentService } from '@domain/agent/agent/agent.service';
1✔
15

1✔
16
@Injectable()
1✔
17
export class AuthenticationService {
1✔
18
  private readonly extendSessionMinRemainingTTL: number | undefined; // min time before session expires when it's already allowed to be extended (in milliseconds)
19

1✔
20
  constructor(
1✔
21
    private agentInfoCacheService: AgentInfoCacheService,
22
    private agentInfoService: AgentInfoService,
1✔
23
    private configService: ConfigService<AlkemioConfig, true>,
24
    private kratosService: KratosService,
25
    private agentService: AgentService,
26
    @Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService
27
  ) {
28
    const { earliest_possible_extend } = this.configService.get(
29
      'identity.authentication.providers.ory',
30
      {
31
        infer: true,
1✔
32
      }
1✔
33
    );
1✔
34

1✔
35
    this.extendSessionMinRemainingTTL = this.parseEarliestPossibleExtend(
1✔
36
      earliest_possible_extend
37
    );
1✔
38
  }
39

40
  public async getAgentInfo(opts: {
1✔
41
    cookie?: string;
42
    authorization?: string;
43
    guestName?: string;
44
  }): Promise<AgentInfo> {
1✔
45
    let session: Session | undefined;
46
    try {
47
      session = await this.kratosService.getSession(
1✔
48
        opts.authorization,
49
        opts.cookie
50
      );
51
      if (session?.identity) {
1✔
52
        const oryIdentity = session.identity as OryDefaultIdentitySchema;
53
        return this.createAgentInfo(oryIdentity);
54
      }
55
    } catch (error) {
56
      this.logger.verbose?.(
57
        `Session validation failed, falling back to guest/anonymous: ${error}`,
58
        LogContext.AUTH
59
      );
60
    }
1✔
61

62
    if (opts.guestName?.trim()) {
63
      return this.agentInfoService.createGuestAgentInfo(opts.guestName.trim());
64
    }
65
    return this.agentInfoService.createAnonymousAgentInfo();
66
  }
1✔
67

68
  /**
69
   * Creates and returns an `AgentInfo` object based on the provided Ory identity and session.
70
   *
71
   * @param oryIdentity - Optional Ory identity schema containing user traits.
72
   * @param session - Optional session information.
73
   * @returns A promise that resolves to an `AgentInfo` object.
74
   *
75
   * This method performs the following steps:
×
NEW
76
   * 1. Validates the provided Ory identity.
×
77
   * 2. Checks for cached agent information using authenticationID (Kratos identity ID).
NEW
78
   * 3. Builds basic agent information if no cached information is found.
×
79
   * 4. Retrieves additional metadata for the agent.
80
   * 5. Populates the agent information with the retrieved metadata.
NEW
81
   * 6. Caches the agent information using authenticationID as key.
×
NEW
82
   */
×
83
  async createAgentInfo(
84
    oryIdentity?: OryDefaultIdentitySchema,
NEW
85
    session?: Session
×
NEW
86
  ): Promise<AgentInfo> {
×
87
    if (!oryIdentity) return this.agentInfoService.createAnonymousAgentInfo();
88

89
    this.validateEmail(oryIdentity);
90

91
    // Use authenticationID (Kratos identity.id) as cache key - stable across email changes
92
    const authenticationID = oryIdentity.id;
93
    const cachedAgentInfo =
×
94
      await this.agentInfoCacheService.getAgentInfoFromCache(authenticationID);
×
95
    if (cachedAgentInfo) return cachedAgentInfo;
×
96

97
    const agentInfo = this.agentInfoService.buildAgentInfoFromOryIdentity(
98
      oryIdentity,
×
99
      { session }
×
100
    );
×
101

102
    const agentInfoMetadata = await this.agentInfoService.getAgentInfoMetadata(
103
      agentInfo.email,
104
      { authenticationId: agentInfo.authenticationID }
105
    );
106
    if (!agentInfoMetadata) return agentInfo;
×
107

108
    this.agentInfoService.populateAgentInfoWithMetadata(
109
      agentInfo,
×
110
      agentInfoMetadata
111
    );
112

×
113
    await this.agentInfoCacheService.setAgentInfoCache(agentInfo);
114
    return agentInfo;
115
  }
×
116

×
117
  /**
×
118
   * Validates the email trait of the provided Ory identity.
×
119
   *
×
120
   * @param oryIdentity - The Ory identity schema containing traits to be validated.
×
121
   * @throws NotSupportedException - If the email trait is missing or empty.
122
   */
123
  private validateEmail(oryIdentity: OryDefaultIdentitySchema): void {
124
    const oryTraits = oryIdentity.traits;
125
    if (!oryTraits.email || oryTraits.email.length === 0) {
×
126
      throw new NotSupportedException(
×
127
        'Session without email encountered',
128
        LogContext.AUTH
129
      );
130
    }
×
131
  }
132

133
  public async extendSession(sessionToBeExtended: Session): Promise<void> {
134
    const adminBearerToken = await this.kratosService.getBearerToken();
135

136
    return this.kratosService.tryExtendSession(
×
137
      sessionToBeExtended,
×
138
      adminBearerToken
139
    );
140
  }
141

142
  /**
×
143
   * Determines whether a session should be extended based on its expiration time and a minimum remaining TTL (Time To Live).
144
   *
×
145
   * @param session - The session object containing the expiration time.
146
   * @returns `true` if the session should be extended, `false` otherwise.
147
   *
148
   * The function checks the following conditions:
149
   * - If the session does not have an expiration time (`expires_at`) or the minimum remaining TTL (`extendSessionMinRemainingTTL`) is not set, it returns `false`.
×
150
   * - If the minimum remaining TTL is set to `-1`, it returns `true`, indicating that the session can be extended at any time.
×
151
   * - Otherwise, it calculates the session's expiry time and compares it with the current time plus the minimum remaining TTL to determine if the session should be extended.
152
   */
153
  public shouldExtendSession(session: Session): boolean {
154
    if (!session.expires_at || !this.extendSessionMinRemainingTTL) {
155
      return false;
×
156
    }
157
    if (this.extendSessionMinRemainingTTL === -1) {
×
158
      return true; // Set to -1 if specified as lifespan in config, meaning it can be extended at any time
×
159
    }
×
160

161
    const expiry = new Date(session.expires_at);
162
    return Date.now() >= expiry.getTime() - this.extendSessionMinRemainingTTL;
×
163
  }
164

×
165
  /**
×
166
   * Parses the `earliestPossibleExtend` parameter to determine the earliest possible time to extend a session.
167
   *
168
   * If the `earliestPossibleExtend` is set to 'lifespan', it returns -1, allowing sessions to be refreshed during their entire lifespan.
169
   * If the `earliestPossibleExtend` is a string, it attempts to parse it as a time duration in HMS format and returns the equivalent milliseconds.
170
   * If the parsing fails or the input is of an unexpected type, it returns `undefined`.
171
   *
×
172
   * @param earliestPossibleExtend - The input value representing the earliest possible time to extend a session. It can be 'lifespan' or a string in HMS format.
173
   * @returns The earliest possible extend time in milliseconds, -1 for 'lifespan', or `undefined` if the input is invalid.
174
   */
×
175
  private parseEarliestPossibleExtend(
176
    earliestPossibleExtend: unknown
×
177
  ): number | undefined {
178
    /**
179
     * If you need high flexibility when extending sessions, you can set earliest_possible_extend to lifespan,
180
     * which allows sessions to be refreshed during their entire lifespan, even right after they are created.
×
181
     * Source https://www.ory.sh/docs/kratos/session-management/refresh-extend-sessions
182
     */
183
    if (earliestPossibleExtend === 'lifespan') {
184
      return -1;
185
    }
186
    if (typeof earliestPossibleExtend === 'string') {
×
187
      const seconds = ConfigUtils.parseHMSString(earliestPossibleExtend);
188
      if (seconds) {
189
        return seconds * 1000;
190
      }
191
    }
192
    return undefined;
193
  }
194
}
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