Skip to content

Commit 185f290

Browse files
authored
add passwordless sdk methods (#18)
1 parent 7ff8365 commit 185f290

File tree

8 files changed

+405
-2
lines changed

8 files changed

+405
-2
lines changed

src/main/java/com/scalekit/ScalekitClient.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ public class ScalekitClient {
2828

2929
private final UserClient userClient;
3030

31+
private final PasswordlessClient passwordlessClient;
32+
3133
private final Webhook webhook;
3234

3335
public ScalekitClient(String siteName, String clientId, String clientSecret) {
@@ -51,6 +53,7 @@ public ScalekitClient(String siteName, String clientId, String clientSecret) {
5153
connectionClient = new ScalekitConnectionClient(channel, credentials);
5254
directoryClient = new ScalekitDirectoryClient(channel, credentials);
5355
userClient = new ScalekitUserClient(channel, credentials);
56+
passwordlessClient = new ScalekitPasswordlessClient(channel, credentials);
5457

5558
webhook = new ScalekitWebhook();
5659

@@ -90,4 +93,8 @@ public UserClient users() {
9093
return this.userClient;
9194
}
9295

96+
public PasswordlessClient passwordless() {
97+
return this.passwordlessClient;
98+
}
99+
93100
}

src/main/java/com/scalekit/api/AuthClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public interface AuthClient {
2020

2121
IdpInitiatedLoginClaims getIdpInitiatedLoginClaims(String idpInitiatedLoginToken) throws APIException;
2222

23-
AuthenticationResponse refreshToken(String refreshToken) throws APIException;
23+
AuthenticationResponse refreshAccessToken(String refreshToken) throws APIException;
2424

2525
Map<String, Object> validateAccessTokenAndGetClaims(String jwt) throws APIException;
2626
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.scalekit.api;
2+
3+
import com.scalekit.grpc.scalekit.v1.auth.passwordless.SendPasswordlessResponse;
4+
import com.scalekit.grpc.scalekit.v1.auth.passwordless.VerifyPasswordLessResponse;
5+
import com.scalekit.internal.http.SendPasswordlessOptions;
6+
import com.scalekit.internal.http.VerifyPasswordlessOptions;
7+
8+
public interface PasswordlessClient {
9+
10+
/**
11+
* Send a passwordless authentication email
12+
* @param email The email address to send the passwordless link to
13+
* @param options The options for sending the passwordless email
14+
* @return SendPasswordlessResponse The response containing auth request details
15+
*/
16+
SendPasswordlessResponse sendPasswordlessEmail(String email, SendPasswordlessOptions options);
17+
18+
/**
19+
* Send a passwordless authentication email with default options
20+
* @param email The email address to send the passwordless link to
21+
* @return SendPasswordlessResponse The response containing auth request details
22+
*/
23+
SendPasswordlessResponse sendPasswordlessEmail(String email);
24+
25+
/**
26+
* Verify a passwordless authentication code or link token
27+
* @param credential The credential to verify (code or linkToken)
28+
* @param authRequestId Optional auth request ID from the send response
29+
* @return VerifyPasswordLessResponse The response containing verification details
30+
*/
31+
VerifyPasswordLessResponse verifyPasswordlessEmail(VerifyPasswordlessOptions credential, String authRequestId);
32+
33+
/**
34+
* Verify a passwordless authentication code or link token without auth request ID
35+
* @param credential The credential to verify (code or linkToken)
36+
* @return VerifyPasswordLessResponse The response containing verification details
37+
*/
38+
VerifyPasswordLessResponse verifyPasswordlessEmail(VerifyPasswordlessOptions credential);
39+
40+
/**
41+
* Resend a passwordless authentication email
42+
* @param authRequestId The auth request ID from the original send response
43+
* @return SendPasswordlessResponse The response containing new auth request details
44+
*/
45+
SendPasswordlessResponse resendPasswordlessEmail(String authRequestId);
46+
}

src/main/java/com/scalekit/api/impl/ScalekitAuthClient.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -347,7 +347,7 @@ public IdpInitiatedLoginClaims getIdpInitiatedLoginClaims(String idpInitiatedLog
347347
* @throws APIException if the refresh token is invalid or expired
348348
*/
349349
@Override
350-
public AuthenticationResponse refreshToken(String refreshToken) throws APIException {
350+
public AuthenticationResponse refreshAccessToken(String refreshToken) throws APIException {
351351
if (refreshToken == null || refreshToken.isEmpty()) {
352352
throw new APIException("refresh token is required");
353353
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
package com.scalekit.api.impl;
2+
3+
import com.scalekit.Environment;
4+
import com.scalekit.api.PasswordlessClient;
5+
import com.scalekit.exceptions.APIException;
6+
import com.scalekit.grpc.scalekit.v1.auth.passwordless.*;
7+
import com.scalekit.internal.RetryExecuter;
8+
import com.scalekit.internal.ScalekitCredentials;
9+
import com.scalekit.internal.http.SendPasswordlessOptions;
10+
import com.scalekit.internal.http.VerifyPasswordlessOptions;
11+
import io.grpc.ManagedChannel;
12+
import io.grpc.StatusRuntimeException;
13+
14+
import java.util.concurrent.TimeUnit;
15+
16+
public class ScalekitPasswordlessClient implements PasswordlessClient {
17+
18+
private final PasswordlessServiceGrpc.PasswordlessServiceBlockingStub passwordlessStub;
19+
private final ScalekitCredentials credentials;
20+
21+
public ScalekitPasswordlessClient(ManagedChannel channel, ScalekitCredentials credentials) {
22+
try {
23+
this.credentials = credentials;
24+
this.passwordlessStub = PasswordlessServiceGrpc
25+
.newBlockingStub(channel)
26+
.withCallCredentials(credentials);
27+
} catch (StatusRuntimeException e) {
28+
throw new RuntimeException("Error creating Passwordless client", e);
29+
}
30+
}
31+
32+
/**
33+
* Send a passwordless authentication email
34+
* @param email The email address to send the passwordless link to
35+
* @param options The options for sending the passwordless email
36+
* @return SendPasswordlessResponse The response containing auth request details
37+
*/
38+
@Override
39+
public SendPasswordlessResponse sendPasswordlessEmail(String email, SendPasswordlessOptions options) {
40+
if (email == null || email.trim().isEmpty()) {
41+
throw new IllegalArgumentException("Email must be a valid string");
42+
}
43+
44+
return RetryExecuter.executeWithRetry(() -> {
45+
SendPasswordlessRequest.Builder requestBuilder = SendPasswordlessRequest.newBuilder()
46+
.setEmail(email);
47+
48+
if (options != null) {
49+
if (options.getTemplate() != null) {
50+
requestBuilder.setTemplate(options.getTemplate());
51+
}
52+
if (options.getState() != null && !options.getState().trim().isEmpty()) {
53+
requestBuilder.setState(options.getState());
54+
}
55+
if (options.getMagiclinkAuthUri() != null && !options.getMagiclinkAuthUri().trim().isEmpty()) {
56+
requestBuilder.setMagiclinkAuthUri(options.getMagiclinkAuthUri());
57+
}
58+
if (options.getExpiresIn() != null && options.getExpiresIn() > 0) {
59+
requestBuilder.setExpiresIn(options.getExpiresIn());
60+
}
61+
if (options.getTemplateVariables() != null && !options.getTemplateVariables().isEmpty()) {
62+
requestBuilder.putAllTemplateVariables(options.getTemplateVariables());
63+
}
64+
}
65+
66+
return this.passwordlessStub
67+
.withDeadlineAfter(Environment.defaultConfig().timeout, TimeUnit.MILLISECONDS)
68+
.sendPasswordlessEmail(requestBuilder.build());
69+
}, this.credentials);
70+
}
71+
72+
/**
73+
* Send a passwordless authentication email with default options
74+
* @param email The email address to send the passwordless link to
75+
* @return SendPasswordlessResponse The response containing auth request details
76+
*/
77+
@Override
78+
public SendPasswordlessResponse sendPasswordlessEmail(String email) {
79+
return sendPasswordlessEmail(email, null);
80+
}
81+
82+
/**
83+
* Verify a passwordless authentication code or link token
84+
* @param credential The credential to verify (code or linkToken)
85+
* @param authRequestId Optional auth request ID from the send response
86+
* @return VerifyPasswordLessResponse The response containing verification details
87+
*/
88+
@Override
89+
public VerifyPasswordLessResponse verifyPasswordlessEmail(VerifyPasswordlessOptions credential, String authRequestId) {
90+
if (credential == null) {
91+
throw new IllegalArgumentException("Credential cannot be null");
92+
}
93+
if ((credential.getCode() == null || credential.getCode().trim().isEmpty()) &&
94+
(credential.getLinkToken() == null || credential.getLinkToken().trim().isEmpty())) {
95+
throw new IllegalArgumentException("Either code or linkToken must be provided");
96+
}
97+
98+
return RetryExecuter.executeWithRetry(() -> {
99+
VerifyPasswordLessRequest.Builder requestBuilder = VerifyPasswordLessRequest.newBuilder();
100+
101+
if (credential.getCode() != null && !credential.getCode().trim().isEmpty()) {
102+
requestBuilder.setCode(credential.getCode());
103+
} else if (credential.getLinkToken() != null && !credential.getLinkToken().trim().isEmpty()) {
104+
requestBuilder.setLinkToken(credential.getLinkToken());
105+
}
106+
107+
if (authRequestId != null && !authRequestId.trim().isEmpty()) {
108+
requestBuilder.setAuthRequestId(authRequestId);
109+
}
110+
111+
return this.passwordlessStub
112+
.withDeadlineAfter(Environment.defaultConfig().timeout, TimeUnit.MILLISECONDS)
113+
.verifyPasswordlessEmail(requestBuilder.build());
114+
}, this.credentials);
115+
}
116+
117+
/**
118+
* Verify a passwordless authentication code or link token without auth request ID
119+
* @param credential The credential to verify (code or linkToken)
120+
* @return VerifyPasswordLessResponse The response containing verification details
121+
*/
122+
@Override
123+
public VerifyPasswordLessResponse verifyPasswordlessEmail(VerifyPasswordlessOptions credential) {
124+
return verifyPasswordlessEmail(credential, null);
125+
}
126+
127+
/**
128+
* Resend a passwordless authentication email
129+
* @param authRequestId The auth request ID from the original send response
130+
* @return SendPasswordlessResponse The response containing new auth request details
131+
*/
132+
@Override
133+
public SendPasswordlessResponse resendPasswordlessEmail(String authRequestId) {
134+
if (authRequestId == null || authRequestId.trim().isEmpty()) {
135+
throw new IllegalArgumentException("Auth request ID must be provided");
136+
}
137+
138+
return RetryExecuter.executeWithRetry(() -> {
139+
ResendPasswordlessRequest request = ResendPasswordlessRequest.newBuilder()
140+
.setAuthRequestId(authRequestId)
141+
.build();
142+
143+
return this.passwordlessStub
144+
.withDeadlineAfter(Environment.defaultConfig().timeout, TimeUnit.MILLISECONDS)
145+
.resendPasswordlessEmail(request);
146+
}, this.credentials);
147+
}
148+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.scalekit.internal.http;
2+
3+
import com.scalekit.grpc.scalekit.v1.auth.passwordless.TemplateType;
4+
import lombok.Getter;
5+
import lombok.Setter;
6+
7+
import java.util.Map;
8+
9+
@Getter
10+
@Setter
11+
public class SendPasswordlessOptions {
12+
private TemplateType template;
13+
private String state;
14+
private String magiclinkAuthUri;
15+
private Integer expiresIn;
16+
private Map<String, String> templateVariables;
17+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.scalekit.internal.http;
2+
3+
import lombok.Getter;
4+
import lombok.Setter;
5+
6+
@Getter
7+
@Setter
8+
public class VerifyPasswordlessOptions {
9+
private String code; // null if not provided
10+
private String linkToken; // null if not provided
11+
private String authRequestId; // null if not provided
12+
}

0 commit comments

Comments
 (0)