Skip to content

Commit 7155d50

Browse files
authored
Cleanup demos (#6)
* Cleanup demos * Fix CI
1 parent da2c453 commit 7155d50

File tree

16 files changed

+107
-97
lines changed

16 files changed

+107
-97
lines changed

.github/workflows/ci.yml

+1-12
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,6 @@ on:
77
branches:
88
- main
99

10-
env:
11-
INSTALL_RUST_VERSION: "1.73"
12-
1310
jobs:
1411
lint:
1512
runs-on: ubuntu-latest
@@ -145,21 +142,13 @@ jobs:
145142
restore-keys: |
146143
${{ runner.os }}-cocoapods-
147144
148-
- name: Install Rust
149-
if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
150-
run: |
151-
rustup toolchain install ${{ env.INSTALL_RUST_VERSION }} --profile minimal --target x86_64-apple-ios,aarch64-apple-ios,aarch64-apple-ios-sim
152-
rustup default ${{ env.INSTALL_RUST_VERSION }}
153-
154-
- name: Use Rust Cache
155-
uses: Swatinem/rust-cache@v2
156-
157145
- name: Set up Git
158146
if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'
159147
run: |
160148
# Configures the global `git` CLI to be able to access private repos.
161149
# `pod` will make use of this.
162150
git config --global url."https://loam-bot:${{ secrets.ACCESS_TOKEN }}@github.com/juicebox-systems/juicebox-sdk.git".insteadOf '[email protected]:juicebox-systems/juicebox-sdk.git'
151+
git config --global url."https://loam-bot:${{ secrets.ACCESS_TOKEN }}@github.com/juicebox-systems/juicebox-sdk-artifacts.git".insteadOf '[email protected]:juicebox-systems/juicebox-sdk-artifacts.git'
163152
164153
- name: Install cocoapods
165154
if: env.turbo_cache_hit != 1 && steps.cocoapods-cache.outputs.cache-hit != 'true'

android/build.gradle

+1-1
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,6 @@ dependencies {
9797
//noinspection GradleDynamicVersion
9898
implementation "com.facebook.react:react-native:+"
9999
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
100-
implementation "xyz.juicebox:sdk:0.2.1"
100+
implementation "xyz.juicebox:sdk:0.2.2"
101101
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.4"
102102
}

demo/email/README.md

+18-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,23 @@
11
This is a demo of the React Native Juicebox SDK.
22

3-
# Running the Demo
3+
# About the demo
4+
5+
The e-mail demo intends to demonstrate a Juicebox onboarding flow that:
6+
1. Generates Juicebox authentication tokens on a server external to the application
7+
2. Authenticates with that server through a "magic" link sent to the user's email
8+
9+
The example server can be found in the [server](server) directory.
410

5-
>**Note**: Make sure you have completed the [React Native - Environment Setup](https://reactnative.dev/docs/environment-setup) instructions till "Creating a new application" step, before proceeding.
11+
To perform authentication and achieve these goals, onboarding typically looks something like:
12+
1. Request the user's e-mail
13+
2. Send the user's e-mail to the server (`POST /email-token`) to send a confirmation e-mail
14+
3. Receive a single-use token from the user's e-mail
15+
4. Use the single-use token to authenticate with the server (`GET /auth-token`) to validate the user's e-mail and receive a server token
16+
5. Request the user's PIN
17+
6. Use the server token to request Juicebox tokens (`POST /juicebox-token`) for the configured realms
18+
7. Perform the appropriate Juicebox operation using the SDK (register or recover)
19+
20+
# Running the Demo
621

722
## Step 1: Start the Metro Server
823

@@ -46,7 +61,7 @@ yarn ios
4661

4762
If everything is set up _correctly_, you should see the demo running in your _Android Emulator_ or _iOS Simulator_ shortly provided you have set up your emulator/simulator correctly.
4863

49-
Yu can also run it directly from within Android Studio and Xcode respectively by opening the respective projects.
64+
You can also run it directly from within Android Studio and Xcode respectively by opening the respective projects.
5065

5166
# Server
5267

demo/email/ios/EmailDemo.xcodeproj/project.pbxproj

+2-2
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@
573573
"$(inherited)",
574574
"@executable_path/Frameworks",
575575
);
576-
MARKETING_VERSION = 1;
576+
MARKETING_VERSION = 1.0;
577577
OTHER_LDFLAGS = (
578578
"$(inherited)",
579579
"-ObjC",
@@ -671,7 +671,7 @@
671671
"$(inherited)",
672672
"@executable_path/Frameworks",
673673
);
674-
MARKETING_VERSION = 1;
674+
MARKETING_VERSION = 1.0;
675675
OTHER_LDFLAGS = (
676676
"$(inherited)",
677677
"-ObjC",

demo/email/ios/EmailDemo/AppDelegate.mm

-30
Original file line numberDiff line numberDiff line change
@@ -32,37 +32,7 @@ - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserAct
3232
return [RCTLinkingManager application:application
3333
continueUserActivity:userActivity
3434
restorationHandler:restorationHandler];
35-
// if (![userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb] || userActivity.webpageURL == nil) {
36-
// return NO;
37-
// }
38-
//
39-
// return [self handleURL:userActivity.webpageURL];
4035
}
41-
//
42-
//- (BOOL)handleURL:(NSURL *)url {
43-
// if (![url.path isEqualToString:@"/app/confirm"]) {
44-
// return NO;
45-
// }
46-
//
47-
// NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
48-
// for (NSURLQueryItem *item in components.queryItems) {
49-
// if (![item.name isEqualToString:@"token"]) continue;
50-
// _emailToken = item.value;
51-
// break;
52-
// }
53-
//
54-
// self.emailTokenDidUpdate(self.emailToken);
55-
//
56-
// return YES;
57-
//}
58-
//
59-
//- (void)setEmailTokenDidUpdate:(void (^)(NSString * _Nullable))emailTokenDidUpdate
60-
//{
61-
// _emailTokenDidUpdate = emailTokenDidUpdate;
62-
// if (self.emailToken) {
63-
// emailTokenDidUpdate(self.emailToken);
64-
// }
65-
//}
6636

6737
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
6838
{

demo/email/ios/Podfile.lock

+2-2
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ PODS:
7373
- hermes-engine (0.72.6):
7474
- hermes-engine/Pre-built (= 0.72.6)
7575
- hermes-engine/Pre-built (0.72.6)
76-
- JuiceboxSdk (0.2.1)
76+
- JuiceboxSdk (0.2.2)
7777
- libevent (2.1.12)
7878
- OpenSSL-Universal (1.1.1100)
7979
- RCT-Folly (2021.07.22.00):
@@ -706,7 +706,7 @@ SPEC CHECKSUMS:
706706
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
707707
glog: 04b94705f318337d7ead9e6d17c019bd9b1f6b1b
708708
hermes-engine: 8057e75cfc1437b178ac86c8654b24e7fead7f60
709-
JuiceboxSdk: d448f1219474f2f43b3c15de83766af0a5db4a70
709+
JuiceboxSdk: 33b89946b31a18fedd3c2c5d11232d0b0e1e9f22
710710
libevent: 4049cae6c81cdb3654a443be001fb9bdceff7913
711711
OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c
712712
RCT-Folly: 424b8c9a7a0b9ab2886ffe9c3b041ef628fd4fb1

demo/email/server/mail/mail.go

+19-14
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@ import (
44
"crypto/tls"
55
"fmt"
66
"html/template"
7+
"io"
78
"net/mail"
89
"net/smtp"
910
"net/url"
1011

1112
"github.com/google/uuid"
13+
"github.com/juicebox-systems/react-native-juicebox-sdk/demo-server/requests"
1214
)
1315

1416
const serverName = "smtp-relay.gmail.com"
@@ -20,24 +22,24 @@ type TemplateOptions struct {
2022
LogoPath string
2123
}
2224

23-
func SendMagicLink(email string, token string) error {
25+
func SendMagicLink(request requests.EmailTokenRequest, token string) error {
2426

2527
uuid, err := uuid.NewRandom()
2628
if err != nil {
2729
return err
2830
}
2931

3032
noReplyAddress := mail.Address{
31-
Name: "Juicebox",
33+
Name: request.AppName,
3234
Address: fmt.Sprintf("no-reply-%[email protected]", uuid.String()),
3335
}
3436

3537
subject := "Confirm your e-mail"
3638

3739
templateOptions := TemplateOptions{
3840
Token: url.QueryEscape(token),
39-
AppName: "Juicebox",
40-
LogoPath: "https://assets-global.website-files.com/64650413ab0c96a6b686cac9/6467eec48e8cabed89c29dc4_juicebox-logo-purple.png",
41+
AppName: request.AppName,
42+
LogoPath: request.LogoPath,
4143
}
4244

4345
bodyTemplate, err := template.ParseFiles("templates/mail.html")
@@ -48,18 +50,11 @@ func SendMagicLink(email string, token string) error {
4850
// Setup headers
4951
headers := make(map[string]string)
5052
headers["From"] = noReplyAddress.String()
51-
headers["To"] = email
53+
headers["To"] = request.Email
5254
headers["Subject"] = subject
5355
headers["MIME-Version"] = "1.0"
5456
headers["Content-Type"] = "text/html; charset=UTF-8"
5557

56-
// Setup message
57-
message := ""
58-
for k, v := range headers {
59-
message += fmt.Sprintf("%s: %s\r\n", k, v)
60-
}
61-
message += "\r\n"
62-
6358
tlsConfig := tls.Config{
6459
InsecureSkipVerify: false,
6560
ServerName: serverName,
@@ -85,7 +80,7 @@ func SendMagicLink(email string, token string) error {
8580
return err
8681
}
8782

88-
if err := client.Rcpt(email); err != nil {
83+
if err := client.Rcpt(request.Email); err != nil {
8984
return err
9085
}
9186

@@ -96,11 +91,21 @@ func SendMagicLink(email string, token string) error {
9691
}
9792
defer wc.Close()
9893

99-
_, err = fmt.Fprint(wc, message)
94+
// Write headers
95+
for k, v := range headers {
96+
_, err = fmt.Fprintf(wc, "%s: %s\r\n", k, v)
97+
if err != nil {
98+
return err
99+
}
100+
}
101+
102+
// End headers
103+
_, err = io.WriteString(wc, "\r\n")
100104
if err != nil {
101105
return err
102106
}
103107

108+
// Write body
104109
err = bodyTemplate.Execute(wc, templateOptions)
105110
if err != nil {
106111
return err

demo/email/server/requests/requests.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package requests
22

33
type EmailTokenRequest struct {
4-
Email string `json:"email"`
4+
Email string `json:"email"`
5+
AppName string `json:"appName"`
6+
LogoPath string `json:"logoPath"`
57
}
68

79
type JuiceboxTokenRequest struct {

demo/email/server/router/router.go

+31-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package router
22

33
import (
4-
"crypto/rand"
4+
cryptoRand "crypto/rand"
55
"encoding/json"
66
"fmt"
77
"io"
@@ -18,11 +18,18 @@ import (
1818
"golang.org/x/crypto/acme/autocert"
1919
)
2020

21+
// Used to sign a JWT token for a Juicebox Realm. In a real word scenario,
22+
// this should be read from some secure secret storage, but for a demo this
23+
// is acceptable.
2124
var realmSigningKey = []byte{0x50, 0x77, 0xa1, 0xfd, 0x9d, 0xfb, 0xd6, 0x0e, 0xd0, 0xc7, 0x65, 0xca, 0x11, 0x4f, 0x67, 0x50, 0x8e, 0x65, 0xa1, 0x85, 0x0d, 0x39, 0x00, 0x19, 0x9e, 0xfc, 0x8a, 0x5f, 0x3d, 0xe6, 0x2c, 0x15}
2225

2326
const realmSigningKeyVersion = 1
2427
const realmTenantName = "juiceboxdemo"
2528

29+
// Used to sign a JWT provided to a user after e-mail verification, which can
30+
// be used for authenticated requests against this server.
31+
// In a real word scenario, this should be read from some secure secret storage,
32+
// but for a demo this is acceptable.
2633
var signingKey = []byte{0xbe, 0xfc, 0x3c, 0xc8, 0x3b, 0xfe, 0xa8, 0x91, 0xc1, 0xad, 0x27, 0xdd, 0x31, 0x9b, 0xe0, 0x33, 0xaf, 0xa3, 0xe9, 0x98, 0x44, 0xb4, 0x41, 0xa9, 0x2c, 0x5e, 0x43, 0x6a, 0x28, 0x1c, 0xdf, 0x1d}
2734

2835
var mutex = sync.Mutex{}
@@ -37,6 +44,9 @@ func RunRouter() {
3744
e.Use(middleware.Recover())
3845
e.Use(middleware.CORS())
3946

47+
// Fetch a JWT token for a Juicebox Realm, using a `JuiceboxTokenRequest`.
48+
//
49+
// Requires authentication with a signed JWT acquired through e-mail verification.
4050
e.POST("/juicebox-token", func(c echo.Context) error {
4151
body, err := io.ReadAll(c.Request().Body)
4252
if err != nil {
@@ -82,6 +92,12 @@ func RunRouter() {
8292
},
8393
}))
8494

95+
// Finishes e-mail verification and acquires an auth token for future requests
96+
// against this server. This token can be used multiple times to request Juicebox
97+
// tokens for multiple realms.
98+
//
99+
// Requires and consumes a one-time signed token sent to the user's e-mail, to verify
100+
// their identity.
85101
e.GET("/auth-token", func(c echo.Context) error {
86102
requestToken, ok := c.Get("user").(*jwt.Token)
87103
if !ok {
@@ -97,6 +113,7 @@ func RunRouter() {
97113
Issuer: "juicebox",
98114
Subject: subject,
99115
NotBefore: jwt.NewNumericDate(time.Now()),
116+
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Minute * 10)),
100117
}
101118

102119
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
@@ -125,6 +142,10 @@ func RunRouter() {
125142
},
126143
}))
127144

145+
// Triggers the send of an e-mail to the user containing a one-time use token
146+
// that can be used to acquire a longer lived authentication token for this server.
147+
//
148+
// Takes the request parameters specified in `EmailTokenRequest`
128149
e.POST("/email-token", func(c echo.Context) error {
129150
body, err := io.ReadAll(c.Request().Body)
130151
if err != nil {
@@ -138,7 +159,10 @@ func RunRouter() {
138159
}
139160

140161
signingKey := make([]byte, 32)
141-
rand.Read(signingKey)
162+
_, err = cryptoRand.Read(signingKey)
163+
if err != nil {
164+
return contextAwareError(c, http.StatusInternalServerError, "Error generating one-time signing key")
165+
}
142166

143167
mutex.Lock()
144168
defer mutex.Unlock()
@@ -159,14 +183,16 @@ func RunRouter() {
159183
return contextAwareError(c, http.StatusBadRequest, "Error signing token")
160184
}
161185

162-
err = mail.SendMagicLink(request.Email, signedToken)
186+
err = mail.SendMagicLink(request, signedToken)
163187
if err != nil {
164188
return contextAwareError(c, http.StatusBadRequest, "Error sending email")
165189
}
166190

167191
return c.NoContent(http.StatusOK)
168192
}, middleware.BodyLimit("2K"))
169193

194+
// Returns the mapping to allow iOS apps to be opened from deep links, e.g. the links
195+
// included in the emails.
170196
e.GET("/.well-known/apple-app-site-association", func(c echo.Context) error {
171197
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
172198
return c.String(http.StatusOK, `
@@ -204,6 +230,8 @@ func RunRouter() {
204230
`)
205231
})
206232

233+
// Returns the mapping to allow android apps to be opened by deep links, e.g. the links
234+
// included in the emails.
207235
e.GET("/.well-known/assetlinks.json", func(c echo.Context) error {
208236
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
209237
return c.String(http.StatusOK, `

demo/email/src/Screens/Email.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,14 @@ const Email = ({ navigation, route }) => {
121121
{
122122
method: 'POST',
123123
headers: { 'Content-Type': 'application/json' },
124-
body: JSON.stringify({ email }),
124+
body: JSON.stringify({
125+
email,
126+
appName: 'Juicebox',
127+
logoPath:
128+
'https://assets-global.website-files.com/64650413ab0c96a6b686cac9/6467eec48e8cabed89c29dc4_juicebox-logo-purple.png',
129+
}),
125130
}
126131
);
127-
128132
setIsLoading(false);
129133

130134
if (response.status === 200) {

0 commit comments

Comments
 (0)