Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,21 @@ try {

```

To deliver the enchanted link by SMS, use the `WithPhone` variants, which take a phone
number as the login ID and return a `PhoneEnchantedLinkResponse` carrying `maskedPhone`.
The SMS carries only the correct link, so there is nothing for the user to choose.

```java
PhoneEnchantedLinkResponse res = null;
try {
String uri = "http://myapp.com/verify-enchanted-link";
res = els.signUpOrInWithPhone(phoneNumber, uri);
} catch (DescopeException de) {
// Handle the error
}

```

After sending the link, you must poll to receive a valid session using the `PendingRef` from
the previous step. A valid session will be returned only after the user clicks the right link.

Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/descope/literals/Routes.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public static class AuthEndPoints {
public static final String VERIFY_ENCHANTED_LINK = "/v1/auth/enchantedlink/verify";
public static final String ENCHANTED_LINK_SESSION = "/v1/auth/enchantedlink/pending-session";
public static final String UPDATE_EMAIL_ENCHANTED_LINK = "/v1/auth/enchantedlink/update/email";
public static final String UPDATE_PHONE_ENCHANTED_LINK = "/v1/auth/enchantedlink/update/phone";

// TOTP
public static final String SIGN_UP_TOTP_LINK = "/v1/auth/totp/signup";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.descope.model.enchantedlink;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PhoneEnchantedLinkResponse {
private String pendingRef;
private String linkId;
private String maskedPhone;
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,10 @@ public class SignUpRequest {
@JsonProperty("URI")
private String uri;
private SignUpOptions loginOptions;
private String phone;

public SignUpRequest(String email, String loginId, User user, String uri,
SignUpOptions loginOptions) {
this(email, loginId, user, uri, loginOptions, null);
}
}
88 changes: 88 additions & 0 deletions src/main/java/com/descope/sdk/auth/EnchantedLinkService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.descope.model.auth.AuthenticationInfo;
import com.descope.model.auth.UpdateOptions;
import com.descope.model.enchantedlink.EnchantedLinkResponse;
import com.descope.model.enchantedlink.PhoneEnchantedLinkResponse;
import com.descope.model.magiclink.LoginOptions;
import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.user.User;
Expand All @@ -27,6 +28,23 @@ EnchantedLinkResponse signIn(
LoginOptions loginOptions)
throws DescopeException;

/**
* Use to login a user based on an enchanted link that will be sent by SMS.
*
* @param loginId - User login ID
* @param uri - Base URI
* @param token - when doing step-up or mfa then we need current session token
* @param loginOptions - {@link LoginOptions LoginOptions}
* @return pendingRef, linkId and masked phone
* @throws DescopeException - error upon failure
*/
PhoneEnchantedLinkResponse signInWithPhone(
String loginId,
String uri,
String token,
LoginOptions loginOptions)
throws DescopeException;

/**
* Use to create a new user based on the given loginID either email or a phone.
*
Expand All @@ -52,6 +70,33 @@ EnchantedLinkResponse signUp(String loginId, String uri, User user)
EnchantedLinkResponse signUp(String loginId, String uri, User user, SignUpOptions signupOptions)
throws DescopeException;

/**
* Use to create a new user with a phone number as the loginID, verified by an enchanted link
* sent by SMS.
*
* @param loginId - User login ID, a phone number
* @param uri - Base URI
* @param user - {@link User User}
* @return pendingRef, linkId and masked phone
* @throws DescopeException - error upon failure
*/
PhoneEnchantedLinkResponse signUpWithPhone(String loginId, String uri, User user)
throws DescopeException;

/**
* Use to create a new user with a phone number as the loginID, verified by an enchanted link
* sent by SMS.
*
* @param loginId - User login ID, a phone number
* @param uri - Base URI
* @param user - {@link User User}
* @param signupOptions - optional claims and template strings
* @return pendingRef, linkId and masked phone
* @throws DescopeException - error upon failure
*/
PhoneEnchantedLinkResponse signUpWithPhone(String loginId, String uri, User user,
SignUpOptions signupOptions) throws DescopeException;

/**
* Use to login in using loginID, if user does not exist, a new user will be created.
*
Expand All @@ -63,6 +108,18 @@ EnchantedLinkResponse signUp(String loginId, String uri, User user, SignUpOption
EnchantedLinkResponse signUpOrIn(String loginId, String uri)
throws DescopeException;

/**
* Use to login in using a phone number as the loginID, if user does not exist, a new user will
* be created. The enchanted link is sent by SMS.
*
* @param loginId - User login ID, a phone number
* @param uri - Base URI
* @return pendingRef, linkId and masked phone
* @throws DescopeException - error upon failure
*/
PhoneEnchantedLinkResponse signUpOrInWithPhone(String loginId, String uri)
throws DescopeException;

/**
* Use to get a session that was generated by SignIn/SignUp request.
*
Expand Down Expand Up @@ -110,4 +167,35 @@ EnchantedLinkResponse updateUserEmail(String loginId, String email, String uri,
*/
EnchantedLinkResponse updateUserEmail(String loginId, String email, String uri, String refreshToken,
UpdateOptions updateOptions, Map<String, String> templateOptions) throws DescopeException;

/**
* Use to update phone and validate via enchanted link sent by SMS.
*
* @param loginId - User login ID
* @param phone - User phone number
* @param uri - Base URI
* @param refreshToken - refresh token to perform the update
* @param updateOptions - update options for the update
* @return {@link PhoneEnchantedLinkResponse} including masked address where the link was sent
* (phone), link to chose and link to retrieve new session from
* @throws DescopeException - error upon failure
*/
PhoneEnchantedLinkResponse updateUserPhone(String loginId, String phone, String uri, String refreshToken,
UpdateOptions updateOptions) throws DescopeException;

/**
* Use to update phone and validate via enchanted link sent by SMS.
*
* @param loginId - User login ID
* @param phone - User phone number
* @param uri - Base URI
* @param refreshToken - refresh token to perform the update
* @param updateOptions - update options for the update
* @param templateOptions - optional parameters for template
* @return {@link PhoneEnchantedLinkResponse} including masked address where the link was sent
* (phone), link to chose and link to retrieve new session from
* @throws DescopeException - error upon failure
*/
PhoneEnchantedLinkResponse updateUserPhone(String loginId, String phone, String uri, String refreshToken,
UpdateOptions updateOptions, Map<String, String> templateOptions) throws DescopeException;
}
123 changes: 108 additions & 15 deletions src/main/java/com/descope/sdk/auth/impl/EnchantedLinkServiceImpl.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
package com.descope.sdk.auth.impl;

import static com.descope.enums.DeliveryMethod.EMAIL;
import static com.descope.enums.DeliveryMethod.SMS;
import static com.descope.literals.Routes.AuthEndPoints.ENCHANTED_LINK_SESSION;
import static com.descope.literals.Routes.AuthEndPoints.SIGN_IN_ENCHANTED_LINK;
import static com.descope.literals.Routes.AuthEndPoints.SIGN_UP_ENCHANTED_LINK;
import static com.descope.literals.Routes.AuthEndPoints.SIGN_UP_OR_IN_ENCHANTED_LINK;
import static com.descope.literals.Routes.AuthEndPoints.UPDATE_EMAIL_ENCHANTED_LINK;
import static com.descope.literals.Routes.AuthEndPoints.UPDATE_PHONE_ENCHANTED_LINK;
import static com.descope.literals.Routes.AuthEndPoints.VERIFY_ENCHANTED_LINK;
import static com.descope.utils.PatternUtils.EMAIL_PATTERN;
import static com.descope.utils.PatternUtils.PHONE_PATTERN;

import com.descope.enums.DeliveryMethod;
import com.descope.exception.DescopeException;
import com.descope.exception.ServerCommonException;
import com.descope.model.auth.AuthenticationInfo;
Expand All @@ -17,12 +21,14 @@
import com.descope.model.enchantedlink.EmptyResponse;
import com.descope.model.enchantedlink.EnchantedLinkResponse;
import com.descope.model.enchantedlink.EnchantedLinkSessionBody;
import com.descope.model.enchantedlink.PhoneEnchantedLinkResponse;
import com.descope.model.jwt.response.JWTResponse;
import com.descope.model.magiclink.LoginOptions;
import com.descope.model.magiclink.SignUpOptions;
import com.descope.model.magiclink.request.SignInRequest;
import com.descope.model.magiclink.request.SignUpRequest;
import com.descope.model.magiclink.request.UpdateEmailRequest;
import com.descope.model.magiclink.request.UpdatePhoneRequest;
import com.descope.model.magiclink.request.VerifyRequest;
import com.descope.model.user.User;
import com.descope.proxy.ApiProxy;
Expand All @@ -41,10 +47,21 @@ class EnchantedLinkServiceImpl extends AuthenticationServiceImpl implements Ench
@Override
public EnchantedLinkResponse signIn(String loginId, String uri, String token, LoginOptions loginOptions)
throws DescopeException {
return signInByDeliveryMethod(EMAIL, loginId, uri, token, loginOptions, EnchantedLinkResponse.class);
}

@Override
public PhoneEnchantedLinkResponse signInWithPhone(String loginId, String uri, String token,
LoginOptions loginOptions) throws DescopeException {
return signInByDeliveryMethod(SMS, loginId, uri, token, loginOptions, PhoneEnchantedLinkResponse.class);
}

private <R> R signInByDeliveryMethod(DeliveryMethod deliveryMethod, String loginId, String uri, String token,
LoginOptions loginOptions, Class<R> responseClass) throws DescopeException {
if (StringUtils.isBlank(loginId)) {
throw ServerCommonException.invalidArgument("Login ID");
}
URI enchantedLink = composeEnchantedLinkSignInURL();
URI enchantedLink = composeEnchantedLinkSignInURL(deliveryMethod);
SignInRequest signInRequest = new SignInRequest(uri, loginId, loginOptions);
ApiProxy apiProxy;
if (JwtUtils.isJWTRequired(loginOptions)) {
Expand All @@ -55,7 +72,7 @@ public EnchantedLinkResponse signIn(String loginId, String uri, String token, Lo
} else {
apiProxy = getApiProxy();
}
return apiProxy.post(enchantedLink, signInRequest, EnchantedLinkResponse.class);
return apiProxy.post(enchantedLink, signInRequest, responseClass);
}

@Override
Expand All @@ -67,32 +84,67 @@ public EnchantedLinkResponse signUp(String loginId, String uri, User user)
@Override
public EnchantedLinkResponse signUp(String loginId, String uri, User user, SignUpOptions signupOptions)
throws DescopeException {
return signUpByDeliveryMethod(EMAIL, loginId, uri, user, signupOptions, EnchantedLinkResponse.class);
}

@Override
public PhoneEnchantedLinkResponse signUpWithPhone(String loginId, String uri, User user)
throws DescopeException {
return signUpWithPhone(loginId, uri, user, null);
}

@Override
public PhoneEnchantedLinkResponse signUpWithPhone(String loginId, String uri, User user,
SignUpOptions signupOptions) throws DescopeException {
return signUpByDeliveryMethod(SMS, loginId, uri, user, signupOptions, PhoneEnchantedLinkResponse.class);
}

private <R> R signUpByDeliveryMethod(DeliveryMethod deliveryMethod, String loginId, String uri, User user,
SignUpOptions signupOptions, Class<R> responseClass) throws DescopeException {
if (user == null) {
user = new User();
}
URI enchantedLinkSignUpURL = composeEnchantedLinkSignUpURL();
URI enchantedLinkSignUpURL = composeEnchantedLinkSignUpURL(deliveryMethod);
SignUpRequest.SignUpRequestBuilder signUpRequestBuilder =
SignUpRequest.builder().loginId(loginId).uri(uri).user(user).email(loginId);
if (StringUtils.isBlank(user.getEmail())) {
user.setEmail(loginId);
SignUpRequest.builder().loginId(loginId).uri(uri);
if (deliveryMethod == SMS) {
signUpRequestBuilder.phone(loginId);
if (StringUtils.isBlank(user.getPhone())) {
user.setPhone(loginId);
}
} else {
signUpRequestBuilder.email(loginId);
if (StringUtils.isBlank(user.getEmail())) {
user.setEmail(loginId);
}
}
if (signupOptions != null) {
signUpRequestBuilder.loginOptions(signupOptions);
}
SignUpRequest signUpRequest = signUpRequestBuilder.user(user).build();
ApiProxy apiProxy = getApiProxy();
return apiProxy.post(enchantedLinkSignUpURL, signUpRequest, EnchantedLinkResponse.class);
return apiProxy.post(enchantedLinkSignUpURL, signUpRequest, responseClass);
}

@Override
public EnchantedLinkResponse signUpOrIn(String loginId, String uri) throws DescopeException {
return signUpOrInByDeliveryMethod(EMAIL, loginId, uri, EnchantedLinkResponse.class);
}

@Override
public PhoneEnchantedLinkResponse signUpOrInWithPhone(String loginId, String uri) throws DescopeException {
return signUpOrInByDeliveryMethod(SMS, loginId, uri, PhoneEnchantedLinkResponse.class);
}

private <R> R signUpOrInByDeliveryMethod(DeliveryMethod deliveryMethod, String loginId, String uri,
Class<R> responseClass) throws DescopeException {
if (StringUtils.isBlank(loginId)) {
throw ServerCommonException.invalidArgument("Login ID");
}
URI magicLinkSignUpOrInURL = composeEnchantedLinkSignUpOrInURL();
URI enchantedLinkSignUpOrInURL = composeEnchantedLinkSignUpOrInURL(deliveryMethod);
SignInRequest signInRequest = new SignInRequest(uri, loginId, null);
ApiProxy apiProxy = getApiProxy();
return apiProxy.post(magicLinkSignUpOrInURL, signInRequest, EnchantedLinkResponse.class);
return apiProxy.post(enchantedLinkSignUpOrInURL, signInRequest, responseClass);
}

@Override
Expand Down Expand Up @@ -152,20 +204,61 @@ public EnchantedLinkResponse updateUserEmail(String loginId, String email, Strin
return apiProxy.post(magicLinkUpdateUserEmail, updateEmailRequest, EnchantedLinkResponse.class);
}

@Override
public PhoneEnchantedLinkResponse updateUserPhone(String loginId, String phone, String uri, String refreshToken,
UpdateOptions updateOptions) throws DescopeException {
return updateUserPhone(loginId, phone, uri, refreshToken, updateOptions, null);
}

@Override
public PhoneEnchantedLinkResponse updateUserPhone(String loginId, String phone, String uri, String refreshToken,
UpdateOptions updateOptions, Map<String, String> templateOptions) throws DescopeException {
if (StringUtils.isBlank(loginId)) {
throw ServerCommonException.invalidArgument("Login ID");
}
if (StringUtils.isBlank(phone) || !PHONE_PATTERN.matcher(phone).matches()) {
throw ServerCommonException.invalidArgument("Phone");
}
if (StringUtils.isBlank(refreshToken)) {
throw ServerCommonException.invalidArgument("Refresh Token");
}
URI enchantedLinkUpdateUserPhone = composeUpdateUserPhoneEnchantedLink();
if (updateOptions == null) {
updateOptions = new UpdateOptions();
}
UpdatePhoneRequest updatePhoneRequest =
UpdatePhoneRequest.builder()
.phone(phone)
.uri(uri)
.loginId(loginId)
.crossDevice(false)
.addToLoginIds(updateOptions.isAddToLoginIds())
.onMergeUseExisting(updateOptions.isOnMergeUseExisting())
.templateOptions(templateOptions)
.build();

ApiProxy apiProxy = getApiProxy(refreshToken);
return apiProxy.post(enchantedLinkUpdateUserPhone, updatePhoneRequest, PhoneEnchantedLinkResponse.class);
}

private URI composeUpdateUserEmailEnchantedLink() {
return getUri(UPDATE_EMAIL_ENCHANTED_LINK);
}

private URI composeEnchantedLinkSignInURL() {
return composeURI(SIGN_IN_ENCHANTED_LINK, EMAIL.getValue());
private URI composeUpdateUserPhoneEnchantedLink() {
return composeURI(UPDATE_PHONE_ENCHANTED_LINK, SMS.getValue());
}

private URI composeEnchantedLinkSignInURL(DeliveryMethod deliveryMethod) {
return composeURI(SIGN_IN_ENCHANTED_LINK, deliveryMethod.getValue());
}

private URI composeEnchantedLinkSignUpURL() {
return composeURI(SIGN_UP_ENCHANTED_LINK, EMAIL.getValue());
private URI composeEnchantedLinkSignUpURL(DeliveryMethod deliveryMethod) {
return composeURI(SIGN_UP_ENCHANTED_LINK, deliveryMethod.getValue());
}

private URI composeEnchantedLinkSignUpOrInURL() {
return composeURI(SIGN_UP_OR_IN_ENCHANTED_LINK, EMAIL.getValue());
private URI composeEnchantedLinkSignUpOrInURL(DeliveryMethod deliveryMethod) {
return composeURI(SIGN_UP_OR_IN_ENCHANTED_LINK, deliveryMethod.getValue());
}

private URI composeVerifyEnchantedLinkURL() {
Expand Down
Loading