Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: [add] Process new_password_required #8

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ This example code demonstrates how to use AWS Cognito with AWS Go SDK in a form
3. Verify user's phone
4. Login with username or refresh token

In order this solution to work, you need to have AWS credentials configured (file `.aws/configuration` exists) and User Pool created in AWS Console. You have to disable "Remember device" and enable "Sms second-factor" on authentication tab.
In order this solution to work, you need create User Pool in AWS Console. You have to disable "Remember device" and enable "Sms second-factor" on authentication tab.

When the app client is created, in it's settings select "Enable username-password (non-SRP) flow for app-based authentication (USER_PASSWORD_AUTH)".

Expand All @@ -31,13 +31,13 @@ go build -o ./build/cognito
Without client secret:

```go
AWS_PROFILE=XXX COGNITO_APP_CLIENT_ID=XXX COGNITO_USER_POOL_ID=XXX PORT=8080 ./build/cognito
AWS_REGION=XXX COGNITO_APP_CLIENT_ID=XXX COGNITO_USER_POOL_ID=XXX PORT=8080 ./build/cognito
```

With client secret:

```go
AWS_PROFILE=XXX COGNITO_APP_CLIENT_ID=XXX COGNITO_APP_CLIENT_SECRET=XXX COGNITO_USER_POOL_ID=XXX PORT=8080 ./build/cognito
AWS_REGION=XXX COGNITO_APP_CLIENT_ID=XXX COGNITO_APP_CLIENT_SECRET=XXX COGNITO_USER_POOL_ID=XXX PORT=8080 ./build/cognito
```

It's worth noting that in production environment you should not pass client secrets this way because with adequate permissions it's possible to read environmental variables of a running process. Also if you call a command that way, secret hash will be stored in your shell history. You should keep those issues in mind and mitigate them in your enviroment.
Expand Down
13 changes: 13 additions & 0 deletions app/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"

"fmt"
"net/http"
"net/url"

"github.com/aws/aws-sdk-go/aws"

Expand Down Expand Up @@ -70,5 +72,16 @@ func (a *App) Login(w http.ResponseWriter, r *http.Request) {
return
}

if res.ChallengeName != nil && *res.ChallengeName == "NEW_PASSWORD_REQUIRED" {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's have NEW_PASSWORD_REQUIRED defined as a constant similar to flowUsernamePassword and flowRefreshToken:

const challengeNamePasswordRequired = "NEW_PASSWORD_REQUIRED"

params = map[string]*string{
"username": aws.String(username),
"session": res.Session,
}
b, _ := json.Marshal(params)
s := url.QueryEscape(string(b))
http.Redirect(w, r, fmt.Sprintf("/new_password_required?params=%s", s), http.StatusFound)
Comment on lines +76 to +82
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea, but I think the data object here is too simple to justify usage of encoding/json and net/url. Since go is about simplicity, could we do this instead?

Suggested change
params = map[string]*string{
"username": aws.String(username),
"session": res.Session,
}
b, _ := json.Marshal(params)
s := url.QueryEscape(string(b))
http.Redirect(w, r, fmt.Sprintf("/new_password_required?params=%s", s), http.StatusFound)
http.Redirect(w, r, fmt.Sprintf("/new_password_required?username=%s&session=%s", username, res.Session), http.StatusFound)

return
}

http.Redirect(w, r, fmt.Sprintf("/login?authres=%s", res.AuthenticationResult), http.StatusFound)
}
44 changes: 44 additions & 0 deletions app/new_password_required.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package app

import (
"fmt"
"log"
"net/http"

"github.com/aws/aws-sdk-go/aws"
cognito "github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
)

// NewPasswordRequired handles set new password.
func (a *App) NewPasswordRequired(w http.ResponseWriter, r *http.Request) {
r.ParseForm()

username := r.Form.Get("username")
password := r.Form.Get("password")
session := r.Form.Get("session")

params := map[string]*string{
"USERNAME": aws.String(username),
"NEW_PASSWORD": aws.String(password),
}
// Compute secret hash based on client secret.
if a.AppClientSecret != "" {
secretHash := computeSecretHash(a.AppClientSecret, username, a.AppClientID)

params["SECRET_HASH"] = aws.String(secretHash)
}
chall := &cognito.RespondToAuthChallengeInput{
ChallengeName: aws.String("NEW_PASSWORD_REQUIRED"),
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Forgot one thing - this should be a constant IMO just like I mentioned here.

ClientId: aws.String(a.AppClientID),
ChallengeResponses: params,
Session: aws.String(session),
}
res, err := a.CognitoClient.RespondToAuthChallenge(chall)
if err != nil {
log.Println(err)
Copy link
Owner

@max-pv max-pv Jun 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this log here :)

http.Redirect(w, r, fmt.Sprintf("/login?message=%s", err.Error()), http.StatusSeeOther)
return
}

http.Redirect(w, r, fmt.Sprintf("/login?authres=%s", res.AuthenticationResult), http.StatusFound)
}
6 changes: 6 additions & 0 deletions app/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ func (a *App) Register(w http.ResponseWriter, r *http.Request) {
},
}

// Compute secret hash based on client secret.
if a.AppClientSecret != "" {
secretHash := computeSecretHash(a.AppClientSecret, username, a.AppClientID)
user.SecretHash = aws.String(secretHash)
}

_, err := a.CognitoClient.SignUp(user)
if err != nil {
fmt.Println(err)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ module github.com/br4in3x/golang-cognito-example

go 1.13

require github.com/aws/aws-sdk-go v1.31.14
require github.com/aws/aws-sdk-go v1.38.40
29 changes: 17 additions & 12 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,23 +1,28 @@
github.com/aws/aws-sdk-go v1.31.14 h1:uRC2riabEXPMHl1CDylsfCod5DKjiOSXhYvxg/Eb9V8=
github.com/aws/aws-sdk-go v1.31.14/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0=
github.com/aws/aws-sdk-go v1.38.40 h1:VVqBFV24tGgXR11tFXPjmR+0ItbnUepbuQjdmhgu3U0=
github.com/aws/aws-sdk-go v1.38.40/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc=
github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
4 changes: 3 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ func Call(a *app.App, w http.ResponseWriter, r *http.Request) {
a.OTP(w, r)
case "/register":
a.Register(w, r)
case "/new_password_required":
a.NewPasswordRequired(w, r)
case "/username":
a.Username(w, r)
default:
Expand All @@ -52,7 +54,7 @@ func Call(a *app.App, w http.ResponseWriter, r *http.Request) {
}

func main() {
conf := &aws.Config{Region: aws.String("us-east-1")}
conf := &aws.Config{Region: aws.String(os.Getenv("AWS_REGION"))}
sess, err := session.NewSession(conf)
if err != nil {
panic(err)
Expand Down
104 changes: 104 additions & 0 deletions static/new_password_required.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.css"
/>
<title>NEW_PASSWORD_REQUIRED</title>
<style>
.holder {
display: flex;
align-items: center;
justify-content: center;
}
.mt-s {
margin-top: 0.7em;
}
code {
font-size: 9px !important;
overflow-x: scroll;
}
</style>
</head>
<body class="holder">
<div class="ui raised text container">
<div class="column">
<h1 class="ui header">NEW_PASSWORD_REQUIRED</h1>
<div class="ui compact message hidden" id="message">
<p>Message goes here...</p>
</div>

<div class="ui styled fluid accordion">
<div class="active title">
<i class="dropdown icon"></i>
Username / Password
</div>
<div class="active content">
<form
class="ui form padded"
method="post"
action="/new_password_required"
>
<div class="field">
<label>Username</label>
<input
type="text"
name="username"
id="username"
readonly
required
placeholder="Please enter your username or refresh token"
/>
<input type="hidden" name="session" id="session" required />
</div>
<div class="field">
<label>NewPassword</label>
<input
type="password"
name="password"
required
placeholder="Wh4t_i5_your_NEW_p@ssw0rd?"
/>
</div>
<button class="ui button" type="submit">Submit</button>
</form>
</div>
</div>
<p class="mt-s">
<a href="/">← Back to index</a>
</p>

<code class="ui compact message hidden" id="params">
Message goes here...
</code>
</div>
</div>
</body>
<script
src="https://code.jquery.com/jquery-3.1.1.min.js"
integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8="
crossorigin="anonymous"
></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/semantic.min.js"></script>
<script>
$(".ui.accordion").accordion();

if (document.location.search.includes("params")) {
try {
const m = document.getElementById("params");
m.style.display = "block";
const params = document.location.search.replace("?params=", "");
const str = decodeURIComponent(params);
const data = JSON.parse(str);
$("#username").val(data.username);
$("#session").val(data.session);
} catch (e) {
console.error(e);
}
Comment on lines +90 to +101
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Based on my comment above for login.go, this would simplify to just reading params from URL, and JSON.parse wouldn't be needed.

}
</script>
</html>