2017-05-25 16:41:45 +00:00
// Copyright 2017 Vector Creations Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package main
import (
2022-08-12 11:00:07 +00:00
"bytes"
"crypto/hmac"
"crypto/sha1"
"encoding/hex"
"encoding/json"
2017-05-25 16:41:45 +00:00
"flag"
"fmt"
2021-06-14 13:08:29 +00:00
"io"
2022-08-12 11:00:07 +00:00
"net/http"
2017-05-25 16:41:45 +00:00
"os"
2022-03-15 11:13:33 +00:00
"regexp"
2021-06-14 13:08:29 +00:00
"strings"
2022-08-12 11:00:07 +00:00
"time"
"github.com/tidwall/gjson"
2017-05-25 16:41:45 +00:00
2020-12-03 10:55:17 +00:00
"github.com/sirupsen/logrus"
2021-06-14 13:08:29 +00:00
"golang.org/x/term"
2022-08-12 11:00:07 +00:00
"github.com/matrix-org/dendrite/setup"
2017-05-25 16:41:45 +00:00
)
const usage = ` Usage : % s
2020-12-03 10:55:17 +00:00
Creates a new user account on the homeserver .
Example :
2021-06-14 13:08:29 +00:00
# provide password by parameter
% s -- config dendrite . yaml - username alice - password foobarbaz
# use password from file
% s -- config dendrite . yaml - username alice - passwordfile my . pass
# ask user to provide password
2022-03-25 13:38:24 +00:00
% s -- config dendrite . yaml - username alice
2021-06-14 13:08:29 +00:00
# read password from stdin
% s -- config dendrite . yaml - username alice - passwordstdin < my . pass
cat my . pass | % s -- config dendrite . yaml - username alice - passwordstdin
2017-05-25 16:41:45 +00:00
Arguments :
`
var (
2022-03-15 11:13:33 +00:00
username = flag . String ( "username" , "" , "The username of the account to register (specify the localpart only, e.g. 'alice' for '@alice:domain.com')" )
2022-03-25 13:38:24 +00:00
password = flag . String ( "password" , "" , "The password to associate with the account" )
2022-03-15 11:13:33 +00:00
pwdFile = flag . String ( "passwordfile" , "" , "The file to use for the password (e.g. for automated account creation)" )
pwdStdin = flag . Bool ( "passwordstdin" , false , "Reads the password from stdin" )
isAdmin = flag . Bool ( "admin" , false , "Create an admin account" )
2022-08-12 11:53:29 +00:00
resetPassword = flag . Bool ( "reset-password" , false , "Deprecated" )
2022-10-02 09:31:40 +00:00
serverURL = flag . String ( "url" , "http://localhost:8008" , "The URL to connect to." )
2022-03-15 11:13:33 +00:00
validUsernameRegex = regexp . MustCompile ( ` ^[0-9a-z_\-=./]+$ ` )
2022-08-23 09:10:41 +00:00
timeout = flag . Duration ( "timeout" , time . Second * 30 , "Timeout for the http client when connecting to the server" )
2017-05-25 16:41:45 +00:00
)
2022-08-12 11:00:07 +00:00
var cl = http . Client {
2022-08-23 09:10:41 +00:00
Timeout : time . Second * 30 ,
2022-08-12 11:00:07 +00:00
Transport : http . DefaultTransport ,
}
2017-05-25 16:41:45 +00:00
func main ( ) {
2021-06-14 13:08:29 +00:00
name := os . Args [ 0 ]
2017-05-25 16:41:45 +00:00
flag . Usage = func ( ) {
2022-08-12 11:53:29 +00:00
_ , _ = fmt . Fprintf ( os . Stderr , usage , name , name , name , name , name , name )
2017-05-25 16:41:45 +00:00
flag . PrintDefaults ( )
}
2020-12-03 10:55:17 +00:00
cfg := setup . ParseFlags ( true )
2017-05-25 16:41:45 +00:00
2022-08-12 11:00:07 +00:00
if * resetPassword {
logrus . Fatalf ( "The reset-password flag has been replaced by the POST /_dendrite/admin/resetPassword/{localpart} admin API." )
}
2022-08-16 11:21:22 +00:00
if cfg . ClientAPI . RegistrationSharedSecret == "" {
logrus . Fatalln ( "Shared secret registration is not enabled, enable it by setting a shared secret in the config: 'client_api.registration_shared_secret'" )
}
2017-05-25 16:41:45 +00:00
if * username == "" {
flag . Usage ( )
os . Exit ( 1 )
}
2022-03-15 11:13:33 +00:00
if ! validUsernameRegex . MatchString ( * username ) {
logrus . Warn ( "Username can only contain characters a-z, 0-9, or '_-./='" )
os . Exit ( 1 )
}
2022-03-25 13:38:24 +00:00
if len ( fmt . Sprintf ( "@%s:%s" , * username , cfg . Global . ServerName ) ) > 255 {
logrus . Fatalf ( "Username can not be longer than 255 characters: %s" , fmt . Sprintf ( "@%s:%s" , * username , cfg . Global . ServerName ) )
}
2022-08-12 11:00:07 +00:00
pass , err := getPassword ( * password , * pwdFile , * pwdStdin , os . Stdin )
if err != nil {
logrus . Fatalln ( err )
2022-03-25 13:38:24 +00:00
}
2021-06-14 13:08:29 +00:00
2022-08-23 09:10:41 +00:00
cl . Timeout = * timeout
2022-08-12 11:00:07 +00:00
accessToken , err := sharedSecretRegister ( cfg . ClientAPI . RegistrationSharedSecret , * serverURL , * username , pass , * isAdmin )
if err != nil {
logrus . Fatalln ( "Failed to create the account:" , err . Error ( ) )
}
logrus . Infof ( "Created account: %s (AccessToken: %s)" , * username , accessToken )
}
2022-05-12 09:35:35 +00:00
2022-08-12 11:00:07 +00:00
type sharedSecretRegistrationRequest struct {
User string ` json:"username" `
Password string ` json:"password" `
Nonce string ` json:"nonce" `
MacStr string ` json:"mac" `
Admin bool ` json:"admin" `
}
2022-05-12 09:35:35 +00:00
2022-08-23 09:10:41 +00:00
func sharedSecretRegister ( sharedSecret , serverURL , localpart , password string , admin bool ) ( accessToken string , err error ) {
registerURL := fmt . Sprintf ( "%s/_synapse/admin/v1/register" , strings . Trim ( serverURL , "/" ) )
2022-08-12 11:00:07 +00:00
nonceReq , err := http . NewRequest ( http . MethodGet , registerURL , nil )
if err != nil {
return "" , fmt . Errorf ( "unable to create http request: %w" , err )
}
nonceResp , err := cl . Do ( nonceReq )
if err != nil {
return "" , fmt . Errorf ( "unable to get nonce: %w" , err )
}
body , err := io . ReadAll ( nonceResp . Body )
2022-05-03 15:35:06 +00:00
if err != nil {
2022-08-12 11:00:07 +00:00
return "" , fmt . Errorf ( "failed to read response body: %w" , err )
2022-05-03 15:35:06 +00:00
}
2022-08-12 11:00:07 +00:00
defer nonceResp . Body . Close ( ) // nolint: errcheck
2017-05-25 16:41:45 +00:00
2022-08-12 11:00:07 +00:00
nonce := gjson . GetBytes ( body , "nonce" ) . Str
adminStr := "notadmin"
if admin {
adminStr = "admin"
}
reg := sharedSecretRegistrationRequest {
User : localpart ,
Password : password ,
Nonce : nonce ,
Admin : admin ,
}
macStr , err := getRegisterMac ( sharedSecret , nonce , localpart , password , adminStr )
if err != nil {
return "" , err
2022-02-16 17:55:38 +00:00
}
2022-08-12 11:00:07 +00:00
reg . MacStr = macStr
2022-03-25 13:38:24 +00:00
2022-08-12 11:00:07 +00:00
js , err := json . Marshal ( reg )
2022-03-25 13:38:24 +00:00
if err != nil {
2022-08-12 11:00:07 +00:00
return "" , fmt . Errorf ( "unable to marshal json: %w" , err )
2022-03-25 13:38:24 +00:00
}
2022-08-12 11:00:07 +00:00
registerReq , err := http . NewRequest ( http . MethodPost , registerURL , bytes . NewBuffer ( js ) )
if err != nil {
return "" , fmt . Errorf ( "unable to create http request: %w" , err )
2022-02-28 12:57:56 +00:00
}
2022-08-12 11:00:07 +00:00
regResp , err := cl . Do ( registerReq )
if err != nil {
return "" , fmt . Errorf ( "unable to create account: %w" , err )
2022-03-25 13:38:24 +00:00
}
2022-08-12 11:00:07 +00:00
defer regResp . Body . Close ( ) // nolint: errcheck
if regResp . StatusCode < 200 || regResp . StatusCode >= 300 {
body , _ = io . ReadAll ( regResp . Body )
return "" , fmt . Errorf ( gjson . GetBytes ( body , "error" ) . Str )
}
r , _ := io . ReadAll ( regResp . Body )
return gjson . GetBytes ( r , "access_token" ) . Str , nil
}
2022-02-28 12:57:56 +00:00
2022-08-12 11:00:07 +00:00
func getRegisterMac ( sharedSecret , nonce , localpart , password , adminStr string ) ( string , error ) {
joined := strings . Join ( [ ] string { nonce , localpart , password , adminStr } , "\x00" )
mac := hmac . New ( sha1 . New , [ ] byte ( sharedSecret ) )
_ , err := mac . Write ( [ ] byte ( joined ) )
2017-05-25 16:41:45 +00:00
if err != nil {
2022-08-12 11:00:07 +00:00
return "" , fmt . Errorf ( "unable to construct mac: %w" , err )
2017-05-25 16:41:45 +00:00
}
2022-08-12 11:00:07 +00:00
regMac := mac . Sum ( nil )
2017-05-25 16:41:45 +00:00
2022-08-12 11:00:07 +00:00
return hex . EncodeToString ( regMac ) , nil
2017-05-25 16:41:45 +00:00
}
2021-06-14 13:08:29 +00:00
2022-03-25 13:38:24 +00:00
func getPassword ( password , pwdFile string , pwdStdin bool , r io . Reader ) ( string , error ) {
2021-06-14 13:08:29 +00:00
// read password from file
2022-03-25 13:38:24 +00:00
if pwdFile != "" {
2022-08-05 09:26:59 +00:00
pw , err := os . ReadFile ( pwdFile )
2021-06-14 13:08:29 +00:00
if err != nil {
2022-03-25 13:38:24 +00:00
return "" , fmt . Errorf ( "Unable to read password from file: %v" , err )
2021-06-14 13:08:29 +00:00
}
2022-03-25 13:38:24 +00:00
return strings . TrimSpace ( string ( pw ) ) , nil
2021-06-14 13:08:29 +00:00
}
// read password from stdin
2022-03-25 13:38:24 +00:00
if pwdStdin {
2022-08-05 09:26:59 +00:00
data , err := io . ReadAll ( r )
2021-06-14 13:08:29 +00:00
if err != nil {
2022-03-25 13:38:24 +00:00
return "" , fmt . Errorf ( "Unable to read password from stdin: %v" , err )
2021-06-14 13:08:29 +00:00
}
2022-03-25 13:38:24 +00:00
return strings . TrimSpace ( string ( data ) ) , nil
2021-06-14 13:08:29 +00:00
}
2022-03-25 13:38:24 +00:00
// If no parameter was set, ask the user to provide the password
if password == "" {
2021-06-14 13:08:29 +00:00
fmt . Print ( "Enter Password: " )
2021-11-02 16:50:59 +00:00
bytePassword , err := term . ReadPassword ( int ( os . Stdin . Fd ( ) ) )
2021-06-14 13:08:29 +00:00
if err != nil {
2022-03-25 13:38:24 +00:00
return "" , fmt . Errorf ( "Unable to read password: %v" , err )
2021-06-14 13:08:29 +00:00
}
fmt . Println ( )
fmt . Print ( "Confirm Password: " )
2021-11-02 16:50:59 +00:00
bytePassword2 , err := term . ReadPassword ( int ( os . Stdin . Fd ( ) ) )
2021-06-14 13:08:29 +00:00
if err != nil {
2022-03-25 13:38:24 +00:00
return "" , fmt . Errorf ( "Unable to read password: %v" , err )
2021-06-14 13:08:29 +00:00
}
fmt . Println ( )
if strings . TrimSpace ( string ( bytePassword ) ) != strings . TrimSpace ( string ( bytePassword2 ) ) {
2022-03-25 13:38:24 +00:00
return "" , fmt . Errorf ( "Entered passwords don't match" )
2021-06-14 13:08:29 +00:00
}
2022-03-25 13:38:24 +00:00
return strings . TrimSpace ( string ( bytePassword ) ) , nil
2021-06-14 13:08:29 +00:00
}
2022-03-25 13:38:24 +00:00
return password , nil
2021-06-14 13:08:29 +00:00
}