2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.binding.windcentrale.internal.api;
15 import static org.eclipse.jetty.http.HttpHeader.ACCEPT;
16 import static org.eclipse.jetty.http.HttpMethod.GET;
17 import static org.openhab.binding.windcentrale.internal.dto.WindcentraleGson.GSON;
19 import java.time.Duration;
20 import java.time.Instant;
21 import java.util.Objects;
22 import java.util.concurrent.ExecutionException;
23 import java.util.concurrent.TimeUnit;
24 import java.util.concurrent.TimeoutException;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.eclipse.jetty.client.api.ContentResponse;
29 import org.openhab.binding.windcentrale.internal.dto.AuthenticationResultResponse;
30 import org.openhab.binding.windcentrale.internal.dto.KeyResponse;
31 import org.openhab.binding.windcentrale.internal.exception.InvalidAccessTokenException;
32 import org.openhab.core.io.net.http.HttpClientFactory;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * Provides the JWT tokens used with the Windcentrale API by using a {@link AuthenticationHelper}.
38 * It also resolves the Windcentrale specific Cognito configuration required by the {@link AuthenticationHelper}.
40 * A token is obtained by calling {@link #getIdToken()}.
41 * The token is cached and returned in subsequent calls to {@link #getIdToken()} until it expires.
42 * When tokens expire they are refreshed using the refresh token when available.
44 * @author Wouter Born - Initial contribution
47 public class TokenProvider {
49 private final Logger logger = LoggerFactory.getLogger(TokenProvider.class);
51 private static final String DEFAULT_USER_POOL_ID = "eu-west-1_U7eYBPrBd";
52 private static final String DEFAULT_CLIENT_ID = "715j3r0trk7o8dqg3md57il7q0";
53 private static final String DEFAULT_REGION = "eu-west-1";
55 private static final String APPLICATION_JSON = "application/json";
56 private static final Duration REQUEST_TIMEOUT = Duration.ofMinutes(1);
57 private static final String KEY_URL = WindcentraleAPI.URL_PREFIX + "/labels/key?domain=mijn.windcentrale.nl";
59 private final HttpClientFactory httpClientFactory;
61 private final String username;
62 private final String password;
64 private @Nullable AuthenticationHelper authenticationHelper;
66 private String idToken = "";
67 private String refreshToken = "";
68 private Instant validityEnd = Instant.MIN;
70 public TokenProvider(HttpClientFactory httpClientFactory, String username, String password) {
71 this.httpClientFactory = httpClientFactory;
72 this.username = username;
73 this.password = password;
76 private AuthenticationHelper createHelper() {
77 String userPoolId = DEFAULT_USER_POOL_ID;
78 String clientId = DEFAULT_CLIENT_ID;
79 String region = DEFAULT_REGION;
82 logger.debug("Getting JSON from: {}", KEY_URL);
83 ContentResponse contentResponse = httpClientFactory.getCommonHttpClient().newRequest(KEY_URL) //
85 .header(ACCEPT, APPLICATION_JSON) //
86 .timeout(REQUEST_TIMEOUT.toNanos(), TimeUnit.NANOSECONDS) //
89 String response = contentResponse.getContentAsString();
90 if (contentResponse.getStatus() >= 400) {
91 logger.debug("Could not get Cognito configuration values, using default values. Error (HTTP {}): {}",
92 contentResponse.getStatus(), contentResponse.getReason());
94 logger.trace("Response: {}", response);
95 KeyResponse keyResponse = Objects.requireNonNullElse(GSON.fromJson(response, KeyResponse.class),
97 if (!keyResponse.userPoolId.isEmpty() && !keyResponse.clientId.isEmpty()
98 && keyResponse.region.isEmpty()) {
99 userPoolId = keyResponse.userPoolId;
100 clientId = keyResponse.clientId;
101 region = keyResponse.region;
104 } catch (ExecutionException | InterruptedException | TimeoutException e) {
105 logger.debug("Could not get Cognito configuration values, using default values", e);
108 logger.debug("Creating new AuthenticationHelper (userPoolId={}, clientId={}, region={})", userPoolId, clientId,
110 return new AuthenticationHelper(httpClientFactory, userPoolId, clientId, region);
113 private AuthenticationHelper getOrCreateHelper() {
114 AuthenticationHelper helper = authenticationHelper;
115 if (helper == null) {
116 helper = createHelper();
117 this.authenticationHelper = helper;
122 public String getIdToken() throws InvalidAccessTokenException {
123 boolean valid = Instant.now().plusSeconds(30).isBefore(validityEnd);
125 logger.debug("Reusing existing valid token");
129 AuthenticationResultResponse result = null;
130 AuthenticationHelper helper = getOrCreateHelper();
132 if (!refreshToken.isBlank()) {
134 logger.debug("Performing token refresh");
135 result = helper.performTokenRefresh(refreshToken);
136 logger.debug("Successfully performed token refresh");
137 } catch (InvalidAccessTokenException e) {
138 logger.debug("Token refresh failed", e);
142 if (result == null) {
143 // there is no refresh token or the refresh failed
144 logger.debug("Performing SRP authentication");
145 result = helper.performSrpAuthentication(username, password);
146 logger.debug("Successfully performed SRP authentication");
148 refreshToken = result.getRefreshToken();
151 idToken = result.getIdToken();
152 validityEnd = Instant.now().plusSeconds(result.getExpiresIn());
153 logger.debug("Token is valid until {}", validityEnd);