Skip to content

Commit

Permalink
docs: add email-otp flow and organize sample flow folders #1749 (#1800)
Browse files Browse the repository at this point in the history
  • Loading branch information
jgomer2001 authored Jul 14, 2022
1 parent 58cbe20 commit 8aea2ee
Show file tree
Hide file tree
Showing 11 changed files with 269 additions and 7 deletions.
2 changes: 1 addition & 1 deletion agama/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ Some of the advantages of using Agama include:

1. Ability to express authentication flows in a clean and concise way
1. Flow composition is supported out-of-the-box: reuse of an existing flow in another requires no effort
1. Reasoning about flows behavior is straightforward (as consequence of points 1 and 2). This makes flow modifications easy
1. Reasoning about flows behavior is easy (as consequence of points 1 and 2). This makes flow modifications and refactoring straightforward
1. Small cognitive load. Agama DSL is a very small language with simple, non-distracting syntax
1. Friendly UI templating engine. No complexities when authoring web pages - stay focused on writing HTML markup
File renamed without changes
16 changes: 16 additions & 0 deletions docs/admin/developer/agama/basic/io.jans.flow.sample.basic
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Flow io.jans.flow.sample.basic
Basepath "samples/basic"

asrv = Call io.jans.as.server.service.AuthenticationService#class
asrv = Call io.jans.service.cdi.util.CdiUtil#bean asrv
obj = {}

Repeat 3 times max
creds = RRF "login.ftlh" obj
obj.success = Call asrv authenticate creds.username creds.password
obj.uid = creds.username

When obj.success is true
Finish obj.uid

Finish false
59 changes: 59 additions & 0 deletions docs/admin/developer/agama/basic/login.ftlh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="${webCtx.contextPath}/servlet/favicon" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<style>
#logo {
max-height: 3.25rem;
margin: 0.5rem;
}
</style>
</head>
<body>

<div class="d-flex flex-column align-items-center justify-content-between min-vh-100 w-100">
<header class="d-flex w-100 justify-content-between border-bottom">
<img id="logo" src="https://gluu.org/wp-content/uploads/2021/02/janssen-project-transparent-630px-182px-300x86.png" />
</header>

<div class="row col-sm-10 col-md-5 mb-5 pb-5">

<div class="border border-1 rounded mb-5 p-5">
<p class="fs-4 mb-5">Welcome</p>

<#if !(success!true)>
<p class="fs-6 text-danger mb-3">${msgs["login.errorMessage"]}</p>
</#if>

<form method="post" enctype="application/x-www-form-urlencoded">
<div class="mb-3 row">
<label for="username" class="col-md-3 col-form-label">${msgs["login.username"]}</label>
<div class="col-md-9">
<input type="text" class="form-control" name="username" id="username" value="${uid!}" required>
</div>
</div>
<div class="mb-4 row">
<label for="password" class="col-md-3 col-form-label">${msgs["login.password"]}</label>
<div class="col-md-9">
<input type="password" class="form-control" id="password" name="password">
</div>
</div>
<div class="row">
<div class="col-md-12 d-flex justify-content-end">
<input type="submit" class="btn btn-success px-4" value="${msgs["login.login"]}">
</div>
</div>
</form>
</div>

</div>
<footer class="d-flex flex-column align-items-center w-100 pb-2">
<hr class="w-75">
</footer>
</div>

</body>
</html>
2 changes: 1 addition & 1 deletion docs/admin/developer/agama/dsl-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,7 @@ The following is a list of reserved words and as such, cannot be used as variabl
|-|
|and|
|is|
|not|
|is not|
|or|

|Special literals|
Expand Down
11 changes: 10 additions & 1 deletion docs/admin/developer/agama/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,8 +75,17 @@ Agama only provides operators for boolean comparison in conditional statements.

## How to concatenate strings in Agama?

See the previous answer.
See the previous answer. A two-lines solution could be:

```
strings = [ s1, s2, ... ]
Call java.lang.String#join "" strings
```

## How to know the index of a given loop iteration?

See the examples in the Looping section of the DSL [full reference](./dsl-full.md#looping).

## Can Agama code be called from Java?

No. These two languages are supposed to play roles that should not be mixed, check [here](./dsl.md#introduction) and [here](./lifecycle.md#design-and-code).
34 changes: 34 additions & 0 deletions docs/admin/developer/agama/otp-email/EmailOTPUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package io.jans.agama.samples;

import io.jans.as.common.service.common.UserService;
import io.jans.service.cdi.util.CdiUtil;
import io.jans.service.MailService;

import java.security.SecureRandom;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class EmailOTPUtil {

private static final int OTP_LENGTH = 6;
private static final String SUBJECT = "Your passcode to get access";
private static final String BODY_TEMPLATE = "Hi, the code to complete your authentication is %s";
private static final SecureRandom RAND = new SecureRandom();

public static String send(String to) {

IntStream digits = RAND.ints(OTP_LENGTH, 0, 10);
String otp = digits.mapToObj(i -> "" + i).collect(Collectors.joining());

String body = String.format(BODY_TEMPLATE, otp);
MailService mailService = CdiUtil.bean(MailService.class);
return mailService.sendMail(to, SUBJECT, body) ? otp : null;

}

public static String emailOf(String username) {
UserService userService = CdiUtil.bean(UserService.class);
return userService.getUser(username).getAttribute("mail");
}

}
38 changes: 38 additions & 0 deletions docs/admin/developer/agama/otp-email/io.jans.flow.sample.otp.email
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
Flow io.jans.flow.sample.otp.email
Basepath "samples/otp-email"

//Launch usr/pwd authentication
obj = Trigger io.jans.flow.sample.basic
When obj.success is false
Finish obj

userId = obj.data.userId
email = Call io.jans.agama.samples.EmailOTPUtil#emailOf userId

When email is null //stop if no e-mail was found
obj = { success: false, error: "Unable to proceed. No e-mail registered for this account" }
Finish obj

sendMail = true
obj = {}
Repeat 3 times max

When sendMail is true
sendMail = false
otpCode = Call io.jans.agama.samples.EmailOTPUtil#send email

When otpCode is null
obj = { success: false, error: "Unable to deliver e-mail message" }
Finish obj

obj = RRF "otp.ftlh" obj

When obj.resend is "" //user clicked on "resend code"
sendMail = true
Otherwise
When obj.passcode is otpCode
Finish userId
obj.matches = false //wrong code entered

obj = { success: false, error: "You have exceeded the number of attempts allowed" }
Finish obj
60 changes: 60 additions & 0 deletions docs/admin/developer/agama/otp-email/otp.ftlh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!doctype html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" href="${webCtx.contextPath}/servlet/favicon" type="image/x-icon">
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet"
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<style>
#logo {
max-height: 3.25rem;
margin: 0.5rem;
}
</style>
</head>
<body>

<div class="d-flex flex-column align-items-center justify-content-between min-vh-100 w-100">
<header class="d-flex w-100 justify-content-between border-bottom">
<img id="logo" src="https://gluu.org/wp-content/uploads/2021/02/janssen-project-transparent-630px-182px-300x86.png" />
</header>

<div class="row col-sm-10 col-md-5 mb-5 pb-5">

<div class="border border-1 rounded mb-5 p-5">
<p class="fs-4 mb-5">Check your e-mail</p>

<#if !(matches!true)>
<p class="fs-6 text-danger mb-3">Wrong code entered</p>
</#if>

<form method="post" enctype="application/x-www-form-urlencoded">
<div class="mb-1 row">
<label for="passcode" class="col-md-5 col-form-label">Enter the passcode code sent</label>
<div class="col-md-7">
<input type="text" class="form-control" id="passcode" name="passcode">
<#-- Pressing the enter key on this field triggers submission via the FIRST submit button found in the page -->
</div>
</div>
<div class="mb-4 row">
<div class="col-md-12 d-flex justify-content-end">
<input type="submit" class="btn btn-success px-4" value="Continue">
</div>
</div>
<div class="row">
<div class="d-flex justify-content-center">
Didn't get a message?
<button type="submit" class="btn btn-link p-0 ps-1" id="resend" name="resend" value="">resend code</button>
</div>
</div>
</form>
</div>

</div>
<footer class="d-flex flex-column align-items-center w-100 pb-2">
<hr class="w-75">
</footer>
</div>

</body>
</html>
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
54 changes: 50 additions & 4 deletions docs/admin/developer/agama/samples.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,17 +8,19 @@ This is the simplest form of authentication where end-users gain access to a pro

### Implementation

![basic authentication](./basic_authn.png)
![basic authentication](./basic/basic_authn.png)

Source code [here](./basic/io.jans.flow.sample.basic).

- Lines 1-2. Flow declarations for qualified name (`io.jans.flow.sample.basic`) and assets folder (`samples/basic`)

- Lines 4-5. Obtains a reference to the server's CDI bean [`AuthenticationService`](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/service/AuthenticationService.java). This object will allow to perform username/password validation. The object's class - `AuthenticationService.class` - is obtained at line 4 and the actual instance is obtained at line 5 by means of lookup carried out by method `bean` of `CdiUtil` class. For more information on the server CDI beans visit [this](TODO) page

- Line 6. Initializes an empty map. It will be passed as data model for the UI page later

- Line 8. Declares that the block of indented statements (lines 9-14) will be executed repeatedly for three times at most
- Line 8. Declares that the block of indented statements (lines 9-14) will be executed repeatedly three times at most

- Line 9. Renders the template `login.ftlh` passing `obj` as data model. The form fields the user will fill at the browser are to be stored at variable `creds`. Find the source code of the template [here](TODO)
- Line 9. Renders the template `login.ftlh` passing `obj` as data model. The form fields the user will fill at the browser are to be stored at variable `creds`. Find the source code of the template [here](./basic/login.ftlh)

- Line 10. Calls the `authenticate` method on `asrv` (the [`AuthenticationService`](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/service/AuthenticationService.java) bean) passing the username and password (sent by the user's browser) as parameters. Note the presence of form fields `username` and `password` in the UI template. The boolean value returned by this method is stored in the key `success` of map `obj` and determines if the username and password combination supplied was valid

Expand All @@ -30,7 +32,7 @@ This is the simplest form of authentication where end-users gain access to a pro

### UI template

Source code of `login.ftlh` can be found [here](TODO). Basically this is 99% HTML markup and will not be explained here. There are only a couple of things to highlight:
Source code of `login.ftlh` can be found [here](./basic/login.ftlh). Basically this is 99% HTML markup and will not be explained here. There are only a couple of things to highlight:

- The conditional `<#if !(success!true)>` (around line 27) is used to determine if an error message should be included in the generated markup. It works this way: if the key `success` exists in this template data model, its value is negated (note the bang character before the left parenthesis) and the `if` condition is evaluated. If non-existing, a `true` value is assumed which is then negated and thus the `if` body will not be evaluated-

Expand All @@ -47,4 +49,48 @@ This is a two-stepped flow where the end-user initially is prompted to enter a u
!!! Note
Acquaintance with the basic authentication flow is required to understand this flow

![email OTP authentication](./otp-email/otp_email.png)

Source code [here](./otp-email/io.jans.flow.sample.otp.email).

- Lines 1-2. Flow declarations for qualified name (`io.jans.flow.sample.otp.email`) and assets folder (`samples/otp-email`)

- Line 5. The [basic authentication](#basic-authentication) flow is launched. Its output stored in variable `obj`

- Lines 6-7. If basic authentication didn't succeed, this flows finishes in the same way

- Line 9. The user identifier is grabbed from the basic flow output and stored in `userId`

- Line 10. The e-mail of the user identified by `userId` is obtained by calling method `emailOf` of class `EmailOTPUtil`. Source code [here](./otp-email/EmailOTPUtil.java). This time the managed bean [`UserService`](https://github.com/JanssenProject/jans/blob/main/jans-auth-server/server/src/main/java/io/jans/as/server/service/UserService.java) is used. Copy `EmailOTPUtil.java` directly to folder `/opt/jans/jetty/jans-auth/agama/scripts/io/jans/agama/samples` of your server

- Lines 12-14. If the user does not have a designated e-mail address, the flow is finished

- Lines 16-18. Initializes utility variables and declares that the block of indented statements (lines 20-35) will be executed repeatedly three times at most

- Lines 20-22. Sends a message with a passcode to the e-mail of the user (stored in variable `email`) by calling method `send` of class `EmailOTPUtil`. If delivery was successful this method returns the passcode sent, otherwise `null`. The value is store in `otpCode` variable

- Lines 24-26. If delivery failed, the flow is finished

- Line 28. Renders the template `otp.ftlh` passing `obj` as data model. Source code of the template [here](./otp-mail/otp.ftlh). After form submission, `obj` will have up to two keys: `passcode` and `resend`. The former will hold whatever the user entered in the passcode text field, and the latter will exist if the "resend code" link was clicked

- Line 31. Variable `sendEmail` is set to true if the user clicked on the "resend code" link

- Line 33-35. If the passcode entered in the text field matches the value of the passcode sent in the e-mail (variable `otpCode`) the flow finishes successfully indicating the identity of the user (previously stored in `userId`). Otherwise key `matches` is attached to map `obj` with value `false`. This key is employed in the template (around line 27) to show an error message in case the user entered a wrong code

- Lines 37-38. If execution reaches this point, it means the form was submitted three times and no matching code was entered. In this case, flow is finished accordingly (failed)

### UI template

Source code of `otp.ftlh` can be found [here](./otp-mail/otp.ftlh). It is on the same lines of the template used in the basic authentication flow. Here the form has three relevant elements:

- A textfield to enter the passcode (around line 35)

- A standard submission button (around line 41)

- A button with `type=submit` and `id=resend` rendered as a hypertext link (around line 47). Only when the link is clicked, `resend` parameter will be part of the request sent to the server

If the user presses the enter key when the focus is on the text field, the form is submitted by means of the standard submission button.

## Combined registration and authentication flow

TODO

0 comments on commit 8aea2ee

Please sign in to comment.