Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
What's new
10
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Open sidebar
RocketChat
Rocket.Chat.Apps-cli
Commits
e0fce7a4
Unverified
Commit
e0fce7a4
authored
Oct 01, 2019
by
Bradley Hilton
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Not yet working draft of authorization with cloud
parent
0e3594b1
Changes
4
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
868 additions
and
46 deletions
+868
-46
package-lock.json
package-lock.json
+656
-22
package.json
package.json
+8
-1
src/commands/submit.ts
src/commands/submit.ts
+66
-23
src/misc/cloudAuth.ts
src/misc/cloudAuth.ts
+138
-0
No files found.
package-lock.json
View file @
e0fce7a4
This diff is collapsed.
Click to expand it.
package.json
View file @
e0fce7a4
...
...
@@ -11,13 +11,16 @@
"url"
:
"
https://github.com/RocketChat/Rocket.Chat.Apps-cli/issues
"
},
"dependencies"
:
{
"
@hapi/hapi
"
:
"
^18.4.0
"
,
"
@oclif/command
"
:
"
^1.5.18
"
,
"
@oclif/config
"
:
"
^1.13.2
"
,
"
@oclif/plugin-help
"
:
"
^2.2.0
"
,
"
@oclif/plugin-not-found
"
:
"
^1.2.2
"
,
"
@rocket.chat/apps-engine
"
:
"
^1.5.1
"
,
"
axios
"
:
"
^0.19.0
"
,
"
chalk
"
:
"
^2.4.2
"
,
"
cli-ux
"
:
"
^5.3.1
"
,
"
conf
"
:
"
^6.1.0
"
,
"
figures
"
:
"
^3.0.0
"
,
"
form-data
"
:
"
^2.5.0
"
,
"
fs-extra
"
:
"
^8.1.0
"
,
...
...
@@ -27,13 +30,16 @@
"
inquirer
"
:
"
^6.5.0
"
,
"
inquirer-checkbox-plus-prompt
"
:
"
^1.0.1
"
,
"
node-fetch
"
:
"
^2.6.0
"
,
"
open
"
:
"
^6.4.0
"
,
"
pascal-case
"
:
"
^2.0.1
"
,
"
pascalcase
"
:
"
^0.1.1
"
,
"
querystring
"
:
"
^0.2.0
"
,
"
semver
"
:
"
^6.3.0
"
,
"
systeminformation
"
:
"
^4.14.8
"
,
"
tslib
"
:
"
^1.10.0
"
,
"
tv4
"
:
"
^1.3.0
"
,
"
typescript
"
:
"
^3.5.3
"
,
"
uuid
"
:
"
^3.3.
2
"
,
"
uuid
"
:
"
^3.3.
3
"
,
"
yazl
"
:
"
^2.5.1
"
},
"devDependencies"
:
{
...
...
@@ -43,6 +49,7 @@
"
@types/chai
"
:
"
^4.1.7
"
,
"
@types/fs-extra
"
:
"
^8.0.0
"
,
"
@types/glob
"
:
"
^7.1.1
"
,
"
@types/hapi__hapi
"
:
"
^18.2.5
"
,
"
@types/inquirer
"
:
"
6.0.3
"
,
"
@types/mocha
"
:
"
^5.2.7
"
,
"
@types/node
"
:
"
^12.6.8
"
,
...
...
src/commands/submit.ts
View file @
e0fce7a4
...
...
@@ -9,19 +9,16 @@ import fetch from 'node-fetch';
import
{
Response
}
from
'
node-fetch
'
;
import
{
AppCompiler
,
AppPackager
,
FolderDetails
,
VariousUtils
}
from
'
../misc
'
;
import
{
CloudAuth
}
from
'
../misc/cloudAuth
'
;
export
default
class
Submit
extends
Command
{
public
static
description
=
'
submits an App to the Marketplace for review
'
;
public
static
flags
=
{
help
:
flags
.
help
({
char
:
'
h
'
}),
// In the future, we will allow people to have their own marketplace instances
// url: flags.string({ description: 'which marketplace should be used' }),
update
:
flags
.
boolean
({
description
:
'
submits an update instead of creating one
'
}),
email
:
flags
.
string
({
char
:
'
e
'
,
description
:
'
the email of the publisher account
'
}),
categories
:
flags
.
string
({
char
:
'
c
'
,
dependsOn
:
[
'
email
'
],
description
:
'
a comma separated list of the categories for the App
'
,
}),
};
...
...
@@ -32,7 +29,7 @@ export default class Submit extends Command {
const
{
flags
}
=
this
.
parse
(
Submit
);
//#region app packaging
cli
.
action
.
start
(
`
${
chalk
.
green
(
'
packaging
'
)
}
your app`
);
cli
.
action
.
start
(
`
${
chalk
.
green
(
'
packaging
'
)}
your app`
);
const
fd
=
new
FolderDetails
(
this
);
...
...
@@ -59,7 +56,7 @@ export default class Submit extends Command {
//#endregion
//#region fetching categories
cli
.
action
.
start
(
`
${
chalk
.
green
(
'
fetching
'
)
}
the available categories`
);
cli
.
action
.
start
(
`
${
chalk
.
green
(
'
fetching
'
)}
the available categories`
);
const
categories
=
await
VariousUtils
.
fetchCategories
();
...
...
@@ -67,19 +64,53 @@ export default class Submit extends Command {
//#endregion
//#region asking for information
if
(
!
flags
.
email
)
{
const
result
=
await
inquirer
.
prompt
([{
type
:
'
input
'
,
name
:
'
email
'
,
message
:
'
What is the publisher
\'
s email address?
'
,
validate
:
(
answer
:
string
)
=>
{
const
regex
=
/^
[^
@
\s]
+@
[^
@
\s]
+
\.[^
@
\s]
+$/g
;
/*
const result = await inquirer.prompt([{
type: 'input',
name: 'email',
message: 'What is the publisher\'s email address?',
validate: (answer: string) => {
const regex = /^[^@\s]+@[^@\s]+\.[^@\s]+$/g;
return regex.test(answer);
},
}]);
*/
return
regex
.
test
(
answer
);
},
const
cloudAuth
=
new
CloudAuth
();
let
email
=
''
;
if
(
!
await
cloudAuth
.
hasToken
())
{
const
cloudAccount
:
any
=
await
inquirer
.
prompt
([{
type
:
'
confirm
'
,
name
:
'
hasAccount
'
,
message
:
'
Have you logged into our Publisher Portal?
'
,
default
:
false
,
}]);
flags
.
email
=
(
result
as
any
).
email
;
if
(
cloudAccount
.
hasAccount
)
{
try
{
cli
.
log
(
chalk
.
green
(
'
*
'
)
+
'
'
+
chalk
.
gray
(
'
waiting for authorization...
'
));
const
r
=
await
cloudAuth
.
executeAuthFlow
();
// tslint:disable-next-line:no-console
console
.
log
(
'
result:
'
,
r
);
}
catch
(
e
)
{
// tslint:disable-next-line:no-console
console
.
log
(
e
);
}
}
else
{
const
result
:
any
=
await
inquirer
.
prompt
([{
type
:
'
input
'
,
name
:
'
email
'
,
message
:
'
What is the publisher
\'
s email address?
'
,
validate
:
(
answer
:
string
)
=>
{
const
regex
=
/^
[^
@
\s]
+@
[^
@
\s]
+
\.[^
@
\s]
+$/g
;
return
regex
.
test
(
answer
);
},
}]);
email
=
result
.
email
;
}
}
if
(
typeof
flags
.
update
===
'
undefined
'
)
{
...
...
@@ -143,32 +174,44 @@ export default class Submit extends Command {
}
//#endregion
cli
.
action
.
start
(
`
${
chalk
.
green
(
'
submitting
'
)
}
your app`
);
cli
.
action
.
start
(
`
${
chalk
.
green
(
'
submitting
'
)}
your app`
);
const
data
=
new
FormData
();
data
.
append
(
'
app
'
,
fs
.
createReadStream
(
fd
.
mergeWithFolder
(
zipName
)));
data
.
append
(
'
email
'
,
flags
.
email
);
data
.
append
(
'
categories
'
,
JSON
.
stringify
(
selectedCategories
));
await
this
.
asyncSubmitData
(
data
,
flags
,
fd
);
if
(
email
)
{
data
.
append
(
'
email
'
,
email
);
}
const
token
=
await
cloudAuth
.
getToken
();
await
this
.
asyncSubmitData
(
data
,
flags
,
fd
,
token
);
cli
.
action
.
stop
(
'
submitted!
'
);
}
// tslint:disable:promise-function-async
private
async
asyncSubmitData
(
data
:
FormData
,
flags
:
{
[
key
:
string
]:
any
},
fd
:
FolderDetails
):
Promise
<
any
>
{
let
url
=
'
https://marketplace.rocket.chat/v1/apps
'
;
// tslint:disable-next-line:max-line-length
private
async
asyncSubmitData
(
data
:
FormData
,
flags
:
{
[
key
:
string
]:
any
},
fd
:
FolderDetails
,
token
:
string
):
Promise
<
any
>
{
let
url
=
'
https://marketplace-beta.rocket.chat/v1/apps
'
;
if
(
flags
.
update
)
{
url
+=
`/
${
fd
.
info
.
id
}
`
;
url
+=
`/
${
fd
.
info
.
id
}
`
;
}
const
headers
:
{
[
key
:
string
]:
string
}
=
{};
if
(
token
)
{
headers
.
Authorization
=
'
Bearer
'
+
token
;
}
const
res
:
Response
=
await
fetch
(
url
,
{
method
:
'
POST
'
,
body
:
data
,
headers
,
});
if
(
res
.
status
!==
200
)
{
const
result
=
await
res
.
json
();
throw
new
Error
(
`Failed to submit the App. Error code
${
result
.
code
}
:
${
result
.
error
}
`
);
throw
new
Error
(
`Failed to submit the App. Error code
${
result
.
code
}
:
${
result
.
error
}
`
);
}
else
{
return
res
.
json
();
}
...
...
src/misc/cloudAuth.ts
0 → 100644
View file @
e0fce7a4
import
{
Request
,
Server
}
from
'
@hapi/hapi
'
;
import
axios
from
'
axios
'
;
import
Conf
=
require
(
'
conf
'
);
import
{
createHash
}
from
'
crypto
'
;
import
open
=
require
(
'
open
'
);
import
{
stringify
}
from
'
querystring
'
;
import
{
cpu
,
mem
,
osInfo
,
system
}
from
'
systeminformation
'
;
import
{
v4
as
uuidv4
}
from
'
uuid
'
;
const
cloudUrl
=
'
https://cloud-beta.rocket.chat
'
;
export
interface
ICloudAuthResult
{
token
:
string
;
}
export
class
CloudAuth
{
private
config
:
Conf
;
private
codeVerifier
:
string
;
private
server
:
Server
;
private
redirectUri
:
string
;
constructor
()
{
this
.
codeVerifier
=
uuidv4
()
+
uuidv4
();
}
public
async
executeAuthFlow
():
Promise
<
ICloudAuthResult
>
{
await
this
.
initialize
();
return
new
Promise
((
resolve
,
reject
)
=>
{
const
port
=
3005
;
try
{
this
.
redirectUri
=
`http://localhost:
${
port
}
/callback`
;
this
.
server
=
new
Server
({
host
:
'
localhost
'
,
port
});
this
.
server
.
route
({
method
:
'
GET
'
,
path
:
'
/callback
'
,
handler
:
async
(
request
:
Request
)
=>
{
try
{
const
code
=
request
.
query
.
code
;
const
token
=
await
this
.
fetchToken
(
code
);
resolve
({
token
});
return
'
Thank you. You can close this tab.
'
;
}
catch
(
err
)
{
reject
(
err
);
}
finally
{
this
.
server
.
stop
();
}
},
});
const
codeChallenge
=
createHash
(
'
sha256
'
).
update
(
this
.
codeVerifier
).
digest
(
'
base64
'
);
const
authorizeUrl
=
this
.
buildAuthorizeUrl
(
codeChallenge
);
open
(
authorizeUrl
);
this
.
server
.
start
();
}
catch
(
e
)
{
// tslint:disable-next-line:no-console
console
.
log
(
'
Error inside of the execute:
'
,
e
);
}
});
}
public
async
hasToken
():
Promise
<
boolean
>
{
await
this
.
initialize
();
return
this
.
config
.
has
(
'
our.a.token
'
);
}
public
async
getToken
():
Promise
<
string
>
{
await
this
.
initialize
();
return
this
.
config
.
get
(
'
our.a.token
'
,
''
)
as
string
;
}
private
async
fetchToken
(
code
:
string
|
Array
<
string
>
):
Promise
<
string
>
{
try
{
const
request
=
{
grant_type
:
'
authorization_code
'
,
redirect_uri
:
this
.
redirectUri
,
client_id
:
'
5d8e59c5d48080ef5497e522
'
,
code
,
code_verifier
:
this
.
codeVerifier
,
};
const
url
=
`
${
cloudUrl
}
/api/oauth/token`
;
const
data
=
stringify
(
request
);
const
res
=
await
axios
.
post
(
url
,
data
);
this
.
config
.
set
(
'
our.a.token
'
,
res
.
data
);
return
res
.
data
;
}
catch
(
err
)
{
// tslint:disable-next-line:no-console
console
.
log
(
'
error getting token
'
,
err
);
throw
err
;
}
}
private
buildAuthorizeUrl
(
codeChallenge
:
string
)
{
const
data
=
{
client_id
:
'
5d8d40d44c43effadb77a351
'
,
response_type
:
'
code
'
,
scope
:
'
offline_access marketplace
'
,
redirect_uri
:
this
.
redirectUri
,
state
:
uuidv4
(),
code_challenge_method
:
'
S256
'
,
code_challenge
:
codeChallenge
,
};
const
params
=
stringify
(
data
);
const
authorizeUrl
=
`
${
cloudUrl
}
/authorize?
${
params
}
`
;
return
authorizeUrl
;
}
private
async
initialize
():
Promise
<
void
>
{
if
(
typeof
this
.
config
!==
'
undefined
'
)
{
return
;
}
this
.
config
=
new
Conf
({
projectName
:
'
Rocket.Chat_apps-cli
'
,
encryptionKey
:
await
this
.
getEncryptionKey
(),
});
}
private
async
getEncryptionKey
():
Promise
<
string
>
{
const
s
=
await
system
();
const
c
=
await
cpu
();
const
m
=
await
mem
();
const
o
=
await
osInfo
();
return
s
.
manufacturer
+
'
;
'
+
s
.
uuid
+
'
;
'
+
String
(
c
.
processors
)
+
'
;
'
+
c
.
vendor
+
'
;
'
+
m
.
total
+
'
;
'
+
o
.
platform
+
'
;
'
+
o
.
release
;
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment