@rpcbase/client 0.148.0 → 0.150.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,114 @@
1
+ /* @flow */
2
+ import assert from "assert"
3
+ import debug from "debug"
4
+ import {useState, useEffect} from "react"
5
+ import {useSearchParam} from "react-use"
6
+ import isEmail from "validator/lib/isEmail"
7
+ import page from "page"
8
+
9
+ import post from "../../helpers/post"
10
+ import {reconnect as rts_reconnect} from "../../rts/rts"
11
+ import {set_is_signed_in, setUid, set_tenant_id} from "../index"
12
+
13
+
14
+ const log = debug("rb:auth:signin")
15
+
16
+ const SignInEmailForm = ({onSuccess, onSuccessRedirect}) => {
17
+ const redirect = useSearchParam("redirect")
18
+
19
+ const [isLoading, setIsLoading] = useState(false)
20
+
21
+ const [email, setEmail] = useState("")
22
+ const [password, setPassword] = useState("")
23
+ const [forgotUrlParam, setForgotUrlParam] = useState(null)
24
+
25
+ const [errors, setErrors] = useState()
26
+
27
+ useEffect(() => {
28
+ if (isEmail(email)) {
29
+ setForgotUrlParam(btoa(email))
30
+ } else {
31
+ setForgotUrlParam("")
32
+ }
33
+ }, [email])
34
+
35
+ const onSubmit = async(e) => {
36
+ e.preventDefault()
37
+ setIsLoading(true)
38
+ const res = await post("/api/v1/auth/sign_in", {email, password})
39
+ setIsLoading(false)
40
+
41
+ // success
42
+ if (res.status === "ok") {
43
+ log("signed in res: ok", res)
44
+ log("redirect to:", redirect || onSuccessRedirect)
45
+ const {user_id} = res
46
+ assert(res.user_id, "missing user_id")
47
+
48
+ setUid(user_id)
49
+ set_tenant_id(user_id.slice(8, 16))
50
+ set_is_signed_in(true)
51
+
52
+ // we must now reconnect on the websocket as we now have a new cookie
53
+ rts_reconnect()
54
+ onSuccess()
55
+ if (redirect) {
56
+ page(redirect)
57
+ } else {
58
+ page(onSuccessRedirect)
59
+ }
60
+ // errors
61
+ } else {
62
+ log("sign in error", res)
63
+
64
+ setErrors(res.errors)
65
+ }
66
+ }
67
+
68
+ const onChangeEmail = (e) => {
69
+ setEmail(e.target.value)
70
+ }
71
+
72
+ const onChangePassword = (e) => {
73
+ setPassword(e.target.value)
74
+ }
75
+
76
+
77
+ return (
78
+ <form onSubmit={onSubmit}>
79
+
80
+ {errors?.form && (
81
+ <p className="text-danger">{errors.form}</p>
82
+ )}
83
+
84
+ <div className="form-floating text-start">
85
+ <input type="email"
86
+ className="form-control"
87
+ id="input-email"
88
+ placeholder="name@example.com"
89
+ value={email}
90
+ onChange={onChangeEmail} />
91
+ <label htmlFor="input-email">Email Address</label>
92
+ </div>
93
+
94
+ <div className="form-floating text-start">
95
+ <input type="password"
96
+ className="form-control"
97
+ id="input-password"
98
+ placeholder="Password"
99
+ value={password}
100
+ onChange={onChangePassword} />
101
+ <label htmlFor="input-password">Password</label>
102
+ </div>
103
+
104
+ <div className="mt-2 text-start w-100">
105
+ <a href={`/forgot-password${forgotUrlParam && "#" + forgotUrlParam}`}>Forgot your password?</a>
106
+ </div>
107
+
108
+ <button className="mt-4 mb-3 w-100 btn btn-lg btn-primary" type="submit" onClick={onSubmit}>Sign In</button>
109
+
110
+ </form>
111
+ )
112
+ }
113
+
114
+ export default SignInEmailForm
@@ -1,87 +1,25 @@
1
1
  /* @flow */
2
2
  import assert from "assert"
3
- import {useState, useEffect} from "react"
4
- import {useSearchParam} from "react-use"
5
- import isEmail from "validator/lib/isEmail"
6
- import page from "page"
7
- import debug from "debug"
8
3
 
9
- import post from "../../helpers/post"
10
- import {reconnect as rts_reconnect} from "../../rts/rts"
4
+ import authConfig from "@rpcbase/dot-rb/auth"
11
5
 
12
- import {set_is_signed_in, setUid, set_tenant_id} from "../index"
6
+ import {AUTH_BUTTONS} from "../../src/ui/oauth"
13
7
  import Footer from "../Footer"
14
8
 
15
- import "./sign-in.scss"
9
+ import SignInEmailForm from "./SignInEmailForm"
16
10
 
11
+ import "./sign-in.scss"
17
12
 
18
- const log = debug("rb:auth:signin")
19
13
 
20
- const SIGNIN_WITH_GITHUB = false
14
+ const hasOAuth = authConfig.oauth_providers?.length > 0
15
+ const hasEmail = authConfig.has_email_signup
21
16
 
22
17
  const SignIn = ({
23
18
  name,
24
19
  logo,
25
- onSuccessRedirect = "/dashboard",
20
+ onSuccessRedirect = "/",
26
21
  onSuccess = () => null
27
22
  }) => {
28
- const [isLoading, setIsLoading] = useState(false)
29
- const [errors, setErrors] = useState()
30
-
31
- const [email, setEmail] = useState("")
32
- const [password, setPassword] = useState("")
33
- const [forgotUrlParam, setForgotUrlParam] = useState(null)
34
-
35
- const redirect = useSearchParam("redirect")
36
-
37
- useEffect(() => {
38
- if (isEmail(email)) {
39
- setForgotUrlParam(btoa(email))
40
- } else {
41
- setForgotUrlParam("")
42
- }
43
- }, [email])
44
-
45
- const onSubmit = async(e) => {
46
- e.preventDefault()
47
- setIsLoading(true)
48
- const res = await post("/api/v1/auth/sign_in", {email, password})
49
- setIsLoading(false)
50
-
51
- // success
52
- if (res.status === "ok") {
53
- log("signed in res: ok", res)
54
- log("redirect to:", redirect || onSuccessRedirect)
55
- const {user_id} = res
56
- assert(res.user_id, "missing user_id")
57
-
58
- setUid(user_id)
59
- set_tenant_id(user_id.slice(8, 16))
60
- set_is_signed_in(true)
61
-
62
- // we must now reconnect on the websocket as we now have a new cookie
63
- rts_reconnect()
64
- onSuccess()
65
- if (redirect) {
66
- page(redirect)
67
- } else {
68
- page(onSuccessRedirect)
69
- }
70
- // errors
71
- } else {
72
- log("sign in error", res)
73
-
74
- setErrors(res.errors)
75
- }
76
- }
77
-
78
- const onChangeEmail = (e) => {
79
- setEmail(e.target.value)
80
- }
81
-
82
- const onChangePassword = (e) => {
83
- setPassword(e.target.value)
84
- }
85
23
 
86
24
  return (
87
25
  <div id="sign-in-wrapper">
@@ -99,66 +37,28 @@ const SignIn = ({
99
37
  <a href="/signup" className="ms-1">Sign up here</a>
100
38
  </p>
101
39
 
102
- <form onSubmit={onSubmit}>
103
-
104
- {errors?.form && (
105
- <p className="text-danger">{errors.form}</p>
106
- )}
107
-
108
- <div className="form-floating text-start">
109
- <input type="email"
110
- className="form-control"
111
- id="input-email"
112
- placeholder="name@example.com"
113
- value={email}
114
- onChange={onChangeEmail} />
115
- <label htmlFor="input-email">Email Address</label>
116
- </div>
117
-
118
- <div className="form-floating text-start">
119
- <input type="password"
120
- className="form-control"
121
- id="input-password"
122
- placeholder="Password"
123
- value={password}
124
- onChange={onChangePassword} />
125
- <label htmlFor="input-password">Password</label>
126
- </div>
127
-
128
- <div className="mt-2 text-start w-100">
129
- <a href={`/forgot-password${forgotUrlParam && "#" + forgotUrlParam}`}>Forgot your password?</a>
130
- </div>
131
-
132
- <button className="mt-4 mb-3 w-100 btn btn-lg btn-primary" type="submit" onClick={onSubmit}>Sign In</button>
133
-
134
- </form>
135
-
136
- {SIGNIN_WITH_GITHUB && (
40
+ {hasEmail && (
41
+ <SignInEmailForm
42
+ onSuccess={onSuccess}
43
+ onSuccessRedirect={onSuccessRedirect}
44
+ />
45
+ )}
46
+
47
+ {hasEmail && hasOAuth && (
137
48
  <>
138
49
  <hr />
139
50
  <p className="text-muted">OR</p>
140
-
141
- <a href="/signup"
142
- className="sign-in-github-btn p-2 btn-lg w-100"
143
- style={{}}>
144
- <div style={{}} className="me-2">
145
- <svg height="28px" width="28px" viewBox="0 0 16 16" style={{fill: "currentColor"}}>
146
- <path fillRule={"evenodd"} d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38
147
- 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01
148
- 1.08.58 1.23.82.72 1.21 1.87.87
149
- 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12
150
- 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08
151
- 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0
152
- .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
153
- </svg>
154
- </div>
155
- <div className="">
156
- Sign in with GitHub
157
- </div>
158
- </a>
159
51
  </>
160
52
  )}
161
53
 
54
+ {hasOAuth && authConfig.oauth_providers.map((provider, index) => {
55
+ const Comp = AUTH_BUTTONS[provider.id]
56
+ assert(Comp, `unable to find oauth button for provider: ${JSON.stringify(provider)}`)
57
+
58
+ return <Comp key={`${provider.id}-${index}`} text="Sign in" />
59
+ })}
60
+
61
+
162
62
  </div>
163
63
  </div>
164
64
 
@@ -7,7 +7,6 @@
7
7
  align-items: center;
8
8
  padding-top: 40px;
9
9
  padding-bottom: 40px;
10
- // background-color: var(--bs-secondary);
11
10
  background-color: $gray-500;
12
11
 
13
12
  hr {
@@ -54,17 +53,4 @@
54
53
  border-top-left-radius: 0;
55
54
  border-top-right-radius: 0;
56
55
  }
57
-
58
- .sign-in-github-btn {
59
- display: inline-flex;
60
- align-items: center;
61
- background-color: #24292E;
62
- font-size: 1.25rem;
63
- color: $gray-200;
64
- text-decoration: none;
65
-
66
- &:hover {
67
- color: $white;
68
- }
69
- }
70
56
  }
@@ -0,0 +1,98 @@
1
+ /* @flow */
2
+ import {useState} from "react"
3
+
4
+ import {reconnect as rts_reconnect} from "../../rts/rts"
5
+
6
+ import {set_is_signed_in, setUid, set_tenant_id} from "../index"
7
+ import post from "../../helpers/post"
8
+
9
+
10
+ const SignUpEmailForm = ({onSuccessRedirect, onSuccess}) => {
11
+ const [email, setEmail] = useState("")
12
+ const [password, setPassword] = useState("")
13
+ const [passwordConfirm, setPasswordConfirm] = useState("")
14
+ const [error, setError] = useState(null)
15
+
16
+ const onSubmit = async() => {
17
+ setError(null)
18
+ if (!password || password !== passwordConfirm) {
19
+ setError("Passwords do not match")
20
+ return
21
+ }
22
+ const res = await post("/public/v1/auth/sign_up", {email, password})
23
+
24
+ if (res.status === "ok") {
25
+ const {user_id} = res
26
+ setUid(user_id)
27
+ set_tenant_id(user_id.slice(8, 16))
28
+ set_is_signed_in(true)
29
+
30
+ // we must now reconnect on the websocket as we have a new cookie
31
+ rts_reconnect()
32
+ onSuccess()
33
+ page(onSuccessRedirect)
34
+ } else if (res.status === "error") {
35
+ setError(res.message)
36
+ }
37
+ }
38
+
39
+ const onChangeEmail = (e) => {
40
+ setEmail(e.target.value)
41
+ }
42
+
43
+ const onChangePassword = (e) => {
44
+ setPassword(e.target.value)
45
+ }
46
+
47
+ const onChangePasswordConfirm = (e) => {
48
+ setPasswordConfirm(e.target.value)
49
+ }
50
+
51
+
52
+ return (
53
+ <>
54
+ {error && (
55
+ <div className="invalid-feedback d-flex text-start">
56
+ {error}
57
+ </div>
58
+ )}
59
+
60
+ <div className="form-floating text-start">
61
+ <input type="email"
62
+ className="form-control"
63
+ id="input-email"
64
+ placeholder="name@example.com"
65
+ value={email}
66
+ onChange={onChangeEmail} />
67
+ <label htmlFor="input-email">Email Address</label>
68
+ </div>
69
+ <div className="form-floating text-start">
70
+ <input type="password"
71
+ className="form-control"
72
+ id="input-password"
73
+ placeholder="Password"
74
+ value={password}
75
+ onChange={onChangePassword} />
76
+ <label htmlFor="input-password">Password</label>
77
+ </div>
78
+ <div className="form-floating text-start">
79
+ <input type="password"
80
+ className="form-control"
81
+ id="input-password-confirm"
82
+ placeholder="Confirm Password"
83
+ value={passwordConfirm}
84
+ onChange={onChangePasswordConfirm} />
85
+ <label htmlFor="input-password-confirm">Confirm Password</label>
86
+ </div>
87
+
88
+ <div className="checkbox mt-3 mb-3 text-start">
89
+ <label style={{cursor: "pointer", userSelect: "none"}}>
90
+ <input type="checkbox" value="terms-accepted" /> I agree to the terms and privacy policy.
91
+ </label>
92
+ </div>
93
+ <button className="w-100 btn btn-lg btn-primary" type="submit" onClick={onSubmit}>Sign Up</button>
94
+ </>
95
+ )
96
+ }
97
+
98
+ export default SignUpEmailForm
@@ -1,61 +1,26 @@
1
1
  /* @flow */
2
- import {useState} from "react"
3
- import page from "page"
2
+ import assert from "assert"
4
3
 
5
- import {reconnect as rts_reconnect} from "../../rts/rts"
4
+ import authConfig from "@rpcbase/dot-rb/auth"
5
+
6
+ import {AUTH_BUTTONS} from "../../src/ui/oauth"
6
7
 
7
- import {set_is_signed_in, setUid, set_tenant_id} from "../index"
8
- import post from "../../helpers/post"
9
8
  import Footer from "../Footer"
10
9
 
10
+ import SignUpEmailForm from "./SignUpEmailForm"
11
+
11
12
  import "./sign-up.scss"
12
13
 
14
+
15
+ const hasOAuth = authConfig.oauth_providers?.length > 0
16
+ const hasEmail = authConfig.has_email_signup
17
+
13
18
  const SignUp = ({
14
19
  logo,
15
20
  name,
16
- onSuccessRedirect = "/dashboard",
21
+ onSuccessRedirect = "/",
17
22
  onSuccess = () => null,
18
23
  }) => {
19
- const [email, setEmail] = useState("")
20
- const [password, setPassword] = useState("")
21
- const [passwordConfirm, setPasswordConfirm] = useState("")
22
- const [error, setError] = useState(null)
23
-
24
- const onSubmit = async() => {
25
- setError(null)
26
- if (!password || password !== passwordConfirm) {
27
- setError("Passwords do not match")
28
- return
29
- }
30
- const res = await post("/public/v1/auth/sign_up", {email, password})
31
-
32
- if (res.status === "ok") {
33
- const {user_id} = res
34
- setUid(user_id)
35
- set_tenant_id(user_id.slice(8, 16))
36
- set_is_signed_in(true)
37
-
38
- // we must now reconnect on the websocket as we have a new cookie
39
- rts_reconnect()
40
- onSuccess()
41
- page(onSuccessRedirect)
42
- } else if (res.status === "error") {
43
- setError(res.message)
44
- }
45
- }
46
-
47
- const onChangeEmail = (e) => {
48
- setEmail(e.target.value)
49
- }
50
-
51
- const onChangePassword = (e) => {
52
- setPassword(e.target.value)
53
- }
54
-
55
- const onChangePasswordConfirm = (e) => {
56
- setPasswordConfirm(e.target.value)
57
- }
58
-
59
24
 
60
25
  return (
61
26
  <div id="sign-up-wrapper">
@@ -68,51 +33,30 @@ const SignUp = ({
68
33
  <hr />
69
34
 
70
35
  <h1 className="h4 mt-3 mb-3 fw-normal">Sign Up</h1>
71
- <p className="text-muted">
36
+ <p className="text-start text-muted">
72
37
  Already have an account ? <a href="/signin" className="ms-1">Sign in here</a>
73
38
  </p>
74
39
 
75
- {error && (
76
- <div className="invalid-feedback d-flex text-start">
77
- {error}
78
- </div>
40
+ {hasEmail && (
41
+ <SignUpEmailForm
42
+ onSuccess={onSuccess}
43
+ onSuccessRedirect={onSuccessRedirect}
44
+ />
79
45
  )}
80
46
 
81
- <div className="form-floating text-start">
82
- <input type="email"
83
- className="form-control"
84
- id="input-email"
85
- placeholder="name@example.com"
86
- value={email}
87
- onChange={onChangeEmail} />
88
- <label htmlFor="input-email">Email Address</label>
89
- </div>
90
- <div className="form-floating text-start">
91
- <input type="password"
92
- className="form-control"
93
- id="input-password"
94
- placeholder="Password"
95
- value={password}
96
- onChange={onChangePassword} />
97
- <label htmlFor="input-password">Password</label>
98
- </div>
99
- <div className="form-floating text-start">
100
- <input type="password"
101
- className="form-control"
102
- id="input-password-confirm"
103
- placeholder="Confirm Password"
104
- value={passwordConfirm}
105
- onChange={onChangePasswordConfirm} />
106
- <label htmlFor="input-password-confirm">Confirm Password</label>
107
- </div>
47
+ {hasEmail && hasOAuth && (
48
+ <>
49
+ <hr />
50
+ <p className="text-muted">OR</p>
51
+ </>
52
+ )}
108
53
 
109
- <div className="checkbox mt-3 mb-3 text-start">
110
- <label style={{cursor: "pointer", userSelect: "none"}}>
111
- <input type="checkbox" value="terms-accepted" /> I agree to the terms and privacy policy.
112
- </label>
113
- </div>
114
- <button className="w-100 btn btn-lg btn-primary" type="submit" onClick={onSubmit}>Sign Up</button>
54
+ {hasOAuth && authConfig.oauth_providers.map((provider, index) => {
55
+ const Comp = AUTH_BUTTONS[provider.id]
56
+ assert(Comp, `unable to find oauth button for provider: ${JSON.stringify(provider)}`)
115
57
 
58
+ return <Comp key={`${provider.id}-${index}`} text="Sign up"/>
59
+ })}
116
60
  </div>
117
61
  </div>
118
62
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rpcbase/client",
3
- "version": "0.148.0",
3
+ "version": "0.150.0",
4
4
  "scripts": {
5
5
  "test": "../../node_modules/.bin/wireit"
6
6
  },
@@ -0,0 +1,26 @@
1
+ /* @flow */
2
+
3
+
4
+ export const GitHub = ({text}) => {
5
+
6
+ return (
7
+ <a href="/signup"
8
+ className="sign-in-github-btn p-2 btn-lg w-100"
9
+ style={{}}>
10
+ <div style={{}} className="me-2">
11
+ <svg height="28px" width="28px" viewBox="0 0 16 16" style={{fill: "currentColor"}}>
12
+ <path fillRule={"evenodd"} d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38
13
+ 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01
14
+ 1.08.58 1.23.82.72 1.21 1.87.87
15
+ 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12
16
+ 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08
17
+ 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0
18
+ .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z"/>
19
+ </svg>
20
+ </div>
21
+ <div className="">
22
+ {text} with GitHub
23
+ </div>
24
+ </a>
25
+ )
26
+ }
@@ -0,0 +1,10 @@
1
+ /* @flow */
2
+
3
+ import "./oauth.scss"
4
+
5
+ import {GitHub} from "./GitHub"
6
+
7
+ // export * from "./GitHub"
8
+ export const AUTH_BUTTONS = {
9
+ github: GitHub,
10
+ }
@@ -0,0 +1,16 @@
1
+ @import "helpers";
2
+
3
+ .sign-in-github-btn {
4
+ display: inline-flex;
5
+ align-items: center;
6
+ background-color: #24292E;
7
+ font-size: 1.25rem;
8
+ color: $gray-200;
9
+ text-decoration: none;
10
+
11
+ border-radius: $border-radius;
12
+
13
+ &:hover {
14
+ color: $white;
15
+ }
16
+ }