Skip to content

Commit 9e5818c

Browse files
authored
Version 2 API (#13)
1 parent c366a2e commit 9e5818c

File tree

100 files changed

+3238
-1415
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

100 files changed

+3238
-1415
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
.env
22
.idea
3+
config.json
34
config.local.json
45
delme.*
56
*.prod.yml

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright 2022 Micah Parks
189+
Copyright 2024 Micah Parks
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

README.md

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,59 @@
11
# magiclinksdev
22

3-
You can find the documentation for this project on the [docs site](https://docs.magiclinks.dev). This site contains
4-
resources for implementing a client and self-hosting the project.
5-
6-
You can find the SaaS landing page at [https://magiclinks.dev](https://magiclinks.dev). Use of the SaaS platform is not
7-
required, but it's very inexpensive and may be cheaper than deploying yourself.
8-
9-
# Getting started
10-
11-
The **magiclinksdev** project is an authentication service that uses magic links to authenticate users. A typical use
12-
case would involve sending a magic link to a user via email. After the user clicks the link, a new authenticated session
13-
is created for that user. Sometimes **magiclinksdev** is abbreviated as "mld".
14-
15-
## About
16-
17-
This project is a magic link authentication service. It serves use cases like:
3+
The **magiclinksdev** project is an authentication service for magic link and One-Time Password (OTP) use cases. There
4+
is built-in email support through Amazon SES and SendGrid.
185

6+
Use cases include:
197
* Sign up
208
* Log in
219
* Password resets
2210
* Email verification
2311
* And more authentication use cases
2412

25-
It can be used to supplement password authentication or replace it entirely.
13+
This project can be used to supplement password authentication or replace it entirely.
14+
15+
If your project has an alternate secure means of communication, you can use generate magic links and OTPs without
16+
sending emails. An example would be mobile push notifications.
2617

27-
A typical use case involves sending a magic link to a user via email. After the user clicks the link, a new
28-
authenticated session is created for that user.
18+
## Getting started
19+
20+
To get started implementing a client application that uses **magiclinksdev** for authentication, the recommended path
21+
is:
22+
1. Do the [quickstart](https://docs.magiclinks.dev/self-host-quickstart)
23+
2. Find a [pre-built SDK](https://docs.magiclinks.dev/client-sdk) or [generate one from the formatted API specification](https://docs.magiclinks.dev/client-api-specification#generate-code)
24+
3. Choose the [magic link](https://docs.magiclinks.dev/client-magic-link-workflow) or [OTP](https://docs.magiclinks.dev/client-otp-workflow) workflow
25+
4. Review the [implementation tips](https://docs.magiclinks.dev/client-implementation-tips) for recommendations and best practices
2926

3027
## Screenshots
3128

32-
The built-in email template is populated on a per-request basis. It adapts to the device's theme automatically. This
33-
template was built using [maizzle](https://maizzle.com/).
29+
The built-in email templates are friendly to mobile and desktop screens. They also adapt to light/dark mode
30+
automatically. The templates are built using [maizzle](https://maizzle.com/).
3431

3532
<span>
36-
<img width="400" src="https://magiclinks.dev/screenshots/mobile-light.png" alt="">
37-
<img width="400" src="https://magiclinks.dev/screenshots/mobile-dark.png" alt="">
33+
<img width="400" src="https://magiclinks.dev/screenshots/magic-link-email-light-mobile-example.png" alt=""/>
34+
<img width="400" src="https://magiclinks.dev/screenshots/magic-link-email-dark-mobile-example.png" alt=""/>
35+
</span>
36+
<span>
37+
<img width="400" src="https://magiclinks.dev/screenshots/otp-email-light-mobile-example.png" alt=""/>
38+
<img width="400" src="https://magiclinks.dev/screenshots/otp-email-dark-mobile-example.png" alt=""/>
3839
</span>
3940

40-
## Suggested Email Workflow
41-
42-
<img width="1000" src="https://magiclinks.dev/mermaid/suggested-email-workflow.png" alt=""/>
43-
44-
## Implementing a client application
41+
## Suggested Magic Link Workflow
42+
<img width="1000" src="https://magiclinks.dev/mermaid/suggested-magic-link-workflow.png" alt=""/>
4543

46-
Client applications are programs that use the **magiclinksdev** project to authenticate their users. Check out the
47-
[**SDKs**](https://docs.magiclinks.dev/sdks) page to get started with an existing SDK. If you can't find an SDK for your
48-
language, you can use the [**Specification**](https://docs.magiclinks.dev/specification) to implement your own client by
49-
hand or code generation. To learn more about the client workflow, check out the
50-
[**Workflow**](https://docs.magiclinks.dev/workflow) page.
44+
## Suggested OTP Workflow
45+
<img width="1000" src="https://magiclinks.dev/mermaid/suggested-otp-workflow.png" alt=""/>
5146

5247
## Self-hosting the service
5348

54-
The **magiclinksdev** project can be self-hosted. Check out the [**Quickstart**](https://docs.magiclinks.dev/quickstart)
55-
page to get started in minutes. For reference on configuring your self-hosted instance, check out the
56-
[**Configuration**](https://docs.magiclinks.dev/configuration).
49+
The **magiclinksdev** project is open-source and can be self-hosted. Check out the [**Quickstart**](https://docs.magiclinks.dev/self-host-quickstart) page
50+
to get started in minutes. For reference on configuring your self-hosted instance, check out the
51+
[**Configuration**](https://docs.magiclinks.dev/self-host-configuration).
5752

5853
## Source code and license
5954

6055
The **magiclinksdev** project is [open source on GitHub](https://github.com/MicahParks/magiclinksdev) and licensed
61-
under [Apache License 2.0](https://github.com/MicahParks/magiclinksdev/blob/master/LICENSE).
56+
under [**Apache License 2.0**](https://github.com/MicahParks/magiclinksdev/blob/master/LICENSE).
6257

6358
## Optional SaaS platform
6459

buildDocker.sh

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,16 @@ if [ "$1" = "push" ]; then
77
then
88
exit 1
99
fi
10-
docker build --pull --push --file nop.Dockerfile --tag micahparks/magiclinksdevmulti .
10+
docker build --pull --push --file multi.Dockerfile --tag micahparks/magiclinksdevmulti .
1111
docker build --pull --push --file nop.Dockerfile --tag micahparks/magiclinksdevnop .
1212
docker build --pull --push --file ses.Dockerfile --tag micahparks/magiclinksdevses .
1313
docker build --pull --push --file sendgrid.Dockerfile --tag micahparks/magiclinksdevsendgrid .
14-
docker build --pull --push --file nop.Dockerfile --tag "micahparks/magiclinksdevmulti:$TAG" .
14+
docker build --pull --push --file multi.Dockerfile --tag "micahparks/magiclinksdevmulti:$TAG" .
1515
docker build --pull --push --file nop.Dockerfile --tag "micahparks/magiclinksdevnop:$TAG" .
1616
docker build --pull --push --file ses.Dockerfile --tag "micahparks/magiclinksdevses:$TAG" .
1717
docker build --pull --push --file sendgrid.Dockerfile --tag "micahparks/magiclinksdevsendgrid:$TAG" .
1818
else
19-
docker build --pull --file nop.Dockerfile --tag micahparks/magiclinksdevmulti .
19+
docker build --pull --file multi.Dockerfile --tag micahparks/magiclinksdevmulti .
2020
docker build --pull --file nop.Dockerfile --tag micahparks/magiclinksdevnop .
2121
docker build --pull --file ses.Dockerfile --tag micahparks/magiclinksdevses .
2222
docker build --pull --file sendgrid.Dockerfile --tag micahparks/magiclinksdevsendgrid .

client/client.go

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ import (
2222
)
2323

2424
const (
25-
// SaaSBaseURL is the base URL for the SaaS offering. The SaaS offering is optional and the magiclinksdev project
26-
// can be self-hosted.
25+
// SaaSBaseURL is the base URL for the SaaS platform. The SaaS platform is optional and the magiclinksdev project
26+
// is open-source and can be self-hosted.
2727
SaaSBaseURL = "https://magiclinks.dev"
2828
// SaaSIss is the iss claim for JWTs in the SaaS offering.
2929
SaaSIss = SaaSBaseURL
@@ -43,7 +43,7 @@ type Options struct {
4343
HTTP *http.Client
4444
}
4545

46-
// Client is a client for the magiclinksdev project.
46+
// Client is the official Golang API client for the magiclinksdev project.
4747
type Client struct {
4848
apiKey uuid.UUID
4949
aud uuid.UUID
@@ -54,9 +54,9 @@ type Client struct {
5454
}
5555

5656
// New creates a new magiclinksdev client. The apiKey and aud are tied to the service account being used. The baseURL is
57-
// the HTTP(S) location of the magiclinksdev deployment. Only use HTTPS in production. For the SaaS offering, use the
57+
// the HTTP(S) location of the magiclinksdev deployment. Only use HTTPS in production. For the SaaS platform, use the
5858
// SaaSBaseURL constant. The iss is the issuer of the JWTs, which is in the configuration of the magiclinksdev
59-
// deployment. For the SaaS offering, use the SaaSIss constant. Providing an empty string for the iss will disable
59+
// deployment. For the SaaS platform, use the SaaSIss constant. Providing an empty string for the iss will disable
6060
// issuer validation.
6161
func New(apiKey, aud uuid.UUID, baseURL, iss string, options Options) (Client, error) {
6262
u, err := url.Parse(baseURL)
@@ -96,8 +96,8 @@ func New(apiKey, aud uuid.UUID, baseURL, iss string, options Options) (Client, e
9696
}
9797

9898
// LocalJWTValidate validates a JWT locally. If the claims argument is not nil, its value will be passed directly to
99-
// jwt.ParseWithClaims. The claims should be unmarshalled into the provided non-nil pointer after the function call. See
100-
// the documentation for jwt.ParseWithClaims for more information. Registered JWT claims will be validated regardless if
99+
// jwt.ParseWithClaims. The claims should be unmarshalled into the claims argument if it is a non-nil pointer. See the
100+
// documentation for jwt.ParseWithClaims for more information. Registered JWT claims will be validated regardless if
101101
// claims are specified or not.
102102
func (c Client) LocalJWTValidate(token string, claims jwt.Claims) (*jwt.Token, error) {
103103
if c.keyf == nil {
@@ -136,15 +136,6 @@ func (c Client) LocalJWTValidate(token string, claims jwt.Claims) (*jwt.Token, e
136136
return t, nil
137137
}
138138

139-
// EmailLinkCreate calls the /email-link/create endpoint and returns the appropriate response.
140-
func (c Client) EmailLinkCreate(ctx context.Context, req model.EmailLinkCreateRequest) (model.EmailLinkCreateResponse, model.Error, error) {
141-
resp, errResp, err := request[model.EmailLinkCreateRequest, model.EmailLinkCreateResponse](ctx, c, http.StatusCreated, network.PathEmailLinkCreate, req)
142-
if err != nil {
143-
return model.EmailLinkCreateResponse{}, errResp, fmt.Errorf("failed to create email link: %w", err)
144-
}
145-
return resp, errResp, nil
146-
}
147-
148139
// JWTCreate calls the /jwt/create endpoint and returns the appropriate response.
149140
func (c Client) JWTCreate(ctx context.Context, req model.JWTCreateRequest) (model.JWTCreateResponse, model.Error, error) {
150141
resp, errResp, err := request[model.JWTCreateRequest, model.JWTCreateResponse](ctx, c, http.StatusCreated, network.PathJWTCreate, req)
@@ -165,16 +156,52 @@ func (c Client) JWTValidate(ctx context.Context, req model.JWTValidateRequest) (
165156
return resp, errResp, nil
166157
}
167158

168-
// LinkCreate calls the /link/create endpoint and returns the appropriate response.
169-
func (c Client) LinkCreate(ctx context.Context, req model.LinkCreateRequest) (model.LinkCreateResponse, model.Error, error) {
170-
resp, errResp, err := request[model.LinkCreateRequest, model.LinkCreateResponse](ctx, c, http.StatusCreated, network.PathLinkCreate, req)
159+
// MagicLinkCreate calls the /magic-link/create endpoint and returns the appropriate response.
160+
func (c Client) MagicLinkCreate(ctx context.Context, req model.MagicLinkCreateRequest) (model.MagicLinkCreateResponse, model.Error, error) {
161+
resp, errResp, err := request[model.MagicLinkCreateRequest, model.MagicLinkCreateResponse](ctx, c, http.StatusCreated, network.PathMagicLinkCreate, req)
162+
if err != nil {
163+
return model.MagicLinkCreateResponse{}, errResp, fmt.Errorf("failed to create link: %w", err)
164+
}
165+
return resp, errResp, nil
166+
}
167+
168+
// MagicLinkEmailCreate calls the /magic-link-email/create endpoint and returns the appropriate response.
169+
func (c Client) MagicLinkEmailCreate(ctx context.Context, req model.MagicLinkEmailCreateRequest) (model.MagicLinkEmailCreateResponse, model.Error, error) {
170+
resp, errResp, err := request[model.MagicLinkEmailCreateRequest, model.MagicLinkEmailCreateResponse](ctx, c, http.StatusCreated, network.PathMagicLinkEmailCreate, req)
171+
if err != nil {
172+
return model.MagicLinkEmailCreateResponse{}, errResp, fmt.Errorf("failed to create email link: %w", err)
173+
}
174+
return resp, errResp, nil
175+
}
176+
177+
// OTPCreate calls the /otp/create endpoint and returns the appropriate response.
178+
func (c Client) OTPCreate(ctx context.Context, req model.OTPCreateRequest) (model.OTPCreateResponse, model.Error, error) {
179+
resp, errResp, err := request[model.OTPCreateRequest, model.OTPCreateResponse](ctx, c, http.StatusCreated, network.PathOTPCreate, req)
180+
if err != nil {
181+
return model.OTPCreateResponse{}, errResp, fmt.Errorf("failed to create OTP: %w", err)
182+
}
183+
return resp, errResp, nil
184+
}
185+
186+
// OTPValidate calls the /otp/validate endpoint and returns the appropriate response.
187+
func (c Client) OTPValidate(ctx context.Context, req model.OTPValidateRequest) (model.OTPValidateResponse, model.Error, error) {
188+
resp, errResp, err := request[model.OTPValidateRequest, model.OTPValidateResponse](ctx, c, http.StatusOK, network.PathOTPValidate, req)
189+
if err != nil {
190+
return model.OTPValidateResponse{}, errResp, fmt.Errorf("failed to validate OTP: %w", err)
191+
}
192+
return resp, errResp, nil
193+
}
194+
195+
// OTPEmailCreate calls the /otp-email/create endpoint and returns the appropriate response.
196+
func (c Client) OTPEmailCreate(ctx context.Context, req model.OTPEmailCreateRequest) (model.OTPEmailCreateResponse, model.Error, error) {
197+
resp, errResp, err := request[model.OTPEmailCreateRequest, model.OTPEmailCreateResponse](ctx, c, http.StatusCreated, network.PathOTPEmailCreate, req)
171198
if err != nil {
172-
return model.LinkCreateResponse{}, errResp, fmt.Errorf("failed to create link: %w", err)
199+
return model.OTPEmailCreateResponse{}, errResp, fmt.Errorf("failed to create email OTP: %w", err)
173200
}
174201
return resp, errResp, nil
175202
}
176203

177-
// Ready calls the /ready endpoint. An error is returned if the service is not ready.
204+
// Ready calls the /ready endpoint. An error is returned if the service is not ready to accept requests.
178205
func (c Client) Ready(ctx context.Context) error {
179206
u, err := c.baseURL.Parse(network.PathReady)
180207
if err != nil {

0 commit comments

Comments
 (0)