aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/routes.go15
-rw-r--r--api/user.go59
-rw-r--r--cmd/web/js/account.jsx120
-rw-r--r--cmd/web/js/api.js8
-rw-r--r--cmd/web/js/main.jsx4
5 files changed, 152 insertions, 54 deletions
diff --git a/api/routes.go b/api/routes.go
index d0e8cec..404f821 100644
--- a/api/routes.go
+++ b/api/routes.go
@@ -48,6 +48,13 @@ var Groups = []Group{
48 {"GET", []gin.HandlerFunc{GetPortfolio}, "/:name/portfolio"}, 48 {"GET", []gin.HandlerFunc{GetPortfolio}, "/:name/portfolio"},
49 }, 49 },
50 }, 50 },
51 {
52 "/user",
53 []Middleware{JwtAuth, UserConfirmed, OtpAuth},
54 []Route{
55 {"GET", []gin.HandlerFunc{UserAccount}, "/account"},
56 },
57 },
51} 58}
52 59
53func Signup(c *gin.Context) { 60func Signup(c *gin.Context) {
@@ -169,3 +176,11 @@ func ConfirmEmail(c *gin.Context) {
169 176
170 RunQuery(query, c) 177 RunQuery(query, c)
171} 178}
179
180func UserAccount(c *gin.Context) {
181 query := &UserAccountQuery{}
182
183 query.In.User = GetUser(c)
184
185 RunQuery(query, c)
186}
diff --git a/api/user.go b/api/user.go
index 2848696..a2737fd 100644
--- a/api/user.go
+++ b/api/user.go
@@ -16,6 +16,26 @@ const (
16 VALID_EMAIL_REGEX = `(?i)^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$` 16 VALID_EMAIL_REGEX = `(?i)^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$`
17) 17)
18 18
19func UserConfirmed(c *gin.Context) *Error {
20 user, exists := c.Get("user")
21
22 if !exists {
23 return &Error{NotAuthorized, "not authorized", fmt.Errorf("no user key in context")}
24 }
25
26 if user.(db.User).Status != db.Confirmed {
27 return &Error{UserNotConfirmed, "user awaiting admin validation", fmt.Errorf("user '%v' not confirmed", user)}
28 }
29
30 return nil
31}
32
33func GetUser(c *gin.Context) db.User {
34 user, _ := c.Get("user")
35
36 return user.(db.User)
37}
38
19func IsValidEmailAddress(email string) bool { 39func IsValidEmailAddress(email string) bool {
20 r := regexp.MustCompile(VALID_EMAIL_REGEX) 40 r := regexp.MustCompile(VALID_EMAIL_REGEX)
21 41
@@ -142,26 +162,6 @@ func (q SigninQuery) Run() (interface{}, *Error) {
142 return SignResult{token}, nil 162 return SignResult{token}, nil
143} 163}
144 164
145func UserConfirmed(c *gin.Context) *Error {
146 user, exists := c.Get("user")
147
148 if !exists {
149 return &Error{NotAuthorized, "not authorized", fmt.Errorf("no user key in context")}
150 }
151
152 if user.(db.User).Status != db.Confirmed {
153 return &Error{UserNotConfirmed, "user awaiting admin validation", fmt.Errorf("user '%v' not confirmed", user)}
154 }
155
156 return nil
157}
158
159func GetUser(c *gin.Context) db.User {
160 user, _ := c.Get("user")
161
162 return user.(db.User)
163}
164
165type ConfirmEmailQuery struct { 165type ConfirmEmailQuery struct {
166 In struct { 166 In struct {
167 Token string 167 Token string
@@ -214,3 +214,22 @@ func (q ConfirmEmailQuery) Run() (interface{}, *Error) {
214 214
215 return nil, nil 215 return nil, nil
216} 216}
217
218type UserAccountQuery struct {
219 In struct {
220 User db.User
221 }
222 Out struct {
223 Email string `json:"email"`
224 }
225}
226
227func (q UserAccountQuery) ValidateParams() *Error {
228 return nil
229}
230
231func (q UserAccountQuery) Run() (interface{}, *Error) {
232 q.Out.Email = q.In.User.Email
233
234 return q.Out, nil
235}
diff --git a/cmd/web/js/account.jsx b/cmd/web/js/account.jsx
index c80be33..3dc8afd 100644
--- a/cmd/web/js/account.jsx
+++ b/cmd/web/js/account.jsx
@@ -1,15 +1,68 @@
1import Api from './api.js'; 1import Api from './api.js';
2import React from 'react'; 2import React from 'react';
3import classnames from 'classnames';
4
5class Panel extends React.Component {
6 render = () => {
7 if (this.props.component === null) {
8 return <div></div>;
9 }
10
11 var className = classnames('row', this.props.topClassName);
12
13 return (
14 <div className={className}>
15 <div className="box col-12">
16 <div className="row">
17 <div className="col-4">{this.props.title}</div>
18 </div>
19 <hr/>
20 {this.props.component}
21 </div>
22 </div>);
23 }
24}
25
26class AccountInformation extends React.Component {
27 constructor(props) {
28 super(props);
29 this.state = {'email': null};
30 }
31
32 loadAccount = () => {
33 Api.Call('USER_ACCOUNT', {}, function(err, status, data) {
34 if (err) {
35 console.error(err, data);
36 return;
37 }
38
39 this.setState({'email': data.email});
40 }.bind(this));
41 }
42
43 componentDidMount = () => {
44 this.loadAccount();
45 }
46
47 render = () => {
48 var component = <p>Loading...</p>;
49 if (this.state.email !== null) {
50 component = <p>Email: {this.state.email}</p>;
51 }
52
53 return component;
54 }
55
56}
3 57
4class PoloniexConfiguration extends React.Component { 58class PoloniexConfiguration extends React.Component {
5 constructor(props) { 59 constructor(props) {
6 super(props); 60 super(props);
7 this.state = {'apiKey': '', 'apiSecret': '', 'apiRequested': false, 'status': 'loading', 'editMode': false}; 61 this.state = {'apiKey': '', 'apiSecret': '', 'status': 'loading', 'editMode': false};
8 } 62 }
9 63
10 checkCredentials = () => { 64 checkCredentials = () => {
11 Api.Call('MARKET_TEST_CREDENTIALS', {'name': 'poloniex'}, function(err, status, data) { 65 Api.Call('MARKET_TEST_CREDENTIALS', {'name': 'poloniex'}, function(err, status, data) {
12 this.setState({'apiRequested': true});
13 if (err) { 66 if (err) {
14 console.error(err, data); 67 console.error(err, data);
15 if (err.code === 'invalid_market_credentials') { 68 if (err.code === 'invalid_market_credentials') {
@@ -53,7 +106,7 @@ class PoloniexConfiguration extends React.Component {
53 106
54 onEditClick = () => { 107 onEditClick = () => {
55 Api.Call('MARKET', {'name': 'poloniex'}, function(err, status, data) { 108 Api.Call('MARKET', {'name': 'poloniex'}, function(err, status, data) {
56 this.setState({'apiRequested': true, 'editMode': true}); 109 this.setState({'editMode': true});
57 if (err) { 110 if (err) {
58 console.error(err, data); 111 console.error(err, data);
59 return; 112 return;
@@ -90,11 +143,8 @@ class PoloniexConfiguration extends React.Component {
90 console.error('unknown status', this.state.status); 143 console.error('unknown status', this.state.status);
91 displayText = null; 144 displayText = null;
92 } 145 }
93 if (this.state.apiRequested === false) { 146
94 return <div></div>;
95 }
96 return ( 147 return (
97 <div>
98 <PoloniexCredentialsForm onLoadCredentials={this.onLoadCredentials} 148 <PoloniexCredentialsForm onLoadCredentials={this.onLoadCredentials}
99 onCredentialsSubmit={this.handleCredentialsSubmit} 149 onCredentialsSubmit={this.handleCredentialsSubmit}
100 onCredentialsChange={this.handleCredentialsChange} 150 onCredentialsChange={this.handleCredentialsChange}
@@ -104,7 +154,6 @@ class PoloniexConfiguration extends React.Component {
104 statusMessage={displayText} 154 statusMessage={displayText}
105 editMode={this.state.editMode} 155 editMode={this.state.editMode}
106 onEditClick={this.onEditClick}/> 156 onEditClick={this.onEditClick}/>
107 </div>
108 ); 157 );
109 } 158 }
110} 159}
@@ -140,33 +189,40 @@ class PoloniexCredentialsForm extends React.Component {
140 } 189 }
141 190
142 return ( 191 return (
143 <div className="row api-credentials-form"> 192 <React.Fragment>
144 <div className="offset-2 col-8 box"> 193 <div className="row config-status">
145 <span className="text-center">Poloniex credentials</span> 194 <div className="col-12">
146 <hr/> 195 <span><i className={iconName}></i>{this.props.statusMessage}</span>
147 <div className="row config-status">
148 <div className="col-12">
149 <span><i className={iconName}></i>{this.props.statusMessage}</span>
150 </div>
151 </div>
152 <div className="row">
153 <div className="col-12">
154 <form role="form" onSubmit={this.handleSubmit}>
155 <label className="w-100">Key:
156 <input className="form-control" type="text" placeholder="key" value={keyDisplayed} onChange={this.handleApiKeyChange} disabled={!this.props.editMode}/>
157 </label>
158 <label className="w-100">Secret:
159 <input className="form-control" type="text" placeholder="secret" value={secretDisplayed} onChange={this.handleApiSecretChange} disabled={!this.props.editMode}/>
160 </label>
161 <input className="form-control submit" type={submitType} value="Save" />
162 <button className="form-control submit" style={{display: buttonDisplay}} onSubmit={null} onClick={this.props.onEditClick} type="button">Show/Edit</button>
163 </form>
164 </div>
165 </div>
166 </div> 196 </div>
167 </div> 197 </div>
198 <div className="row">
199 <div className="col-12">
200 <form role="form" onSubmit={this.handleSubmit}>
201 <label className="w-100">Key:
202 <input className="form-control" type="text" placeholder="key" value={keyDisplayed} onChange={this.handleApiKeyChange} disabled={!this.props.editMode}/>
203 </label>
204 <label className="w-100">Secret:
205 <input className="form-control" type="text" placeholder="secret" value={secretDisplayed} onChange={this.handleApiSecretChange} disabled={!this.props.editMode}/>
206 </label>
207 <input className="form-control submit" type={submitType} value="Save" />
208 <button className="form-control submit" style={{display: buttonDisplay}} onSubmit={null} onClick={this.props.onEditClick} type="button">Show/Edit</button>
209 </form>
210 </div>
211 </div>
212 </React.Fragment>
168 ); 213 );
169 } 214 }
170} 215}
171 216
172export default PoloniexConfiguration; 217class UserAccount extends React.Component {
218 render = () => {
219 return (
220 <React.Fragment>
221 <Panel component={<AccountInformation/>} title="Account" />
222 <Panel component={<PoloniexConfiguration/>} title="Poloniex credentials" topClassName="api-credentials-form" />
223 </React.Fragment>
224 );
225 }
226}
227
228export default UserAccount;
diff --git a/cmd/web/js/api.js b/cmd/web/js/api.js
index 62530ba..63355f0 100644
--- a/cmd/web/js/api.js
+++ b/cmd/web/js/api.js
@@ -116,6 +116,14 @@ var ApiEndpoints = {
116 return '/market/' + params.name + '/update'; 116 return '/market/' + params.name + '/update';
117 } 117 }
118 }, 118 },
119 'USER_ACCOUNT': {
120 'type': 'GET',
121 'auth': true,
122 'parameters': [],
123 'buildUrl': function(params) {
124 return '/user/account';
125 }
126 },
119 'OTP_ENROLL': { 127 'OTP_ENROLL': {
120 'type': 'GET', 128 'type': 'GET',
121 'auth': true, 129 'auth': true,
diff --git a/cmd/web/js/main.jsx b/cmd/web/js/main.jsx
index dfc3337..7fa2d26 100644
--- a/cmd/web/js/main.jsx
+++ b/cmd/web/js/main.jsx
@@ -4,7 +4,7 @@ import PasswordResetForm from './password_reset.js';
4import ChangePasswordForm from './change_password.js'; 4import ChangePasswordForm from './change_password.js';
5import OtpEnrollForm from './otp.js'; 5import OtpEnrollForm from './otp.js';
6import PoloniexController from './poloniex.js'; 6import PoloniexController from './poloniex.js';
7import PoloniexConfiguration from './account.js'; 7import UserAccount from './account.js';
8import App from './app.js'; 8import App from './app.js';
9import Api from './api.js'; 9import Api from './api.js';
10import cookies from './cookies.js'; 10import cookies from './cookies.js';
@@ -97,7 +97,7 @@ App.page('/me', true, function(context) {
97 97
98App.page('/account', true, function(context) { 98App.page('/account', true, function(context) {
99 App.mount(<div> 99 App.mount(<div>
100 <PoloniexConfiguration/> 100 <UserAccount/>
101 </div>); 101 </div>);
102}); 102});
103 103