mirror of
https://github.com/mickael-kerjean/filestash.git
synced 2025-11-01 10:56:31 +08:00
improvement (login): new backend API to generate login form in the frontend
This commit is contained in:
@ -1,5 +1,4 @@
|
||||
/* latin-ext */
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
font-style: normal;
|
||||
@ -8,9 +7,7 @@
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
|
||||
/* latin */
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
font-style: normal;
|
||||
@ -19,9 +16,7 @@
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
|
||||
}
|
||||
|
||||
|
||||
/* latin-ext */
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
font-style: normal;
|
||||
@ -30,9 +25,7 @@
|
||||
unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB, U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
|
||||
}
|
||||
|
||||
|
||||
/* latin */
|
||||
|
||||
@font-face {
|
||||
font-family: 'Source Code Pro';
|
||||
font-style: normal;
|
||||
@ -77,6 +70,10 @@ html {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.center{
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Input, Select, Enabler } from './';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import { Input, Textarea, Select, Enabler } from './';
|
||||
import { FormObjToJSON, bcrypt_password, format } from '../helpers/';
|
||||
|
||||
import "./formbuilder.scss";
|
||||
@ -21,6 +23,15 @@ export class FormBuilder extends React.Component {
|
||||
|
||||
if(Array.isArray(struct)) return null;
|
||||
else if(isALeaf(struct) === false){
|
||||
const [normal, advanced] = function(s){
|
||||
let _normal = [];
|
||||
let _advanced = [];
|
||||
for (let key in s){
|
||||
const tmp = {key: key, data: s[key]};
|
||||
'id' in s[key] ? _advanced.push(tmp) : _normal.push(tmp);
|
||||
}
|
||||
return [_normal, _advanced];
|
||||
}(struct);
|
||||
if(level <= 1){
|
||||
return (
|
||||
<div className="formbuilder">
|
||||
@ -28,14 +39,25 @@ export class FormBuilder extends React.Component {
|
||||
key ? <h2 className="no-select">{ format(key) }</h2> : ""
|
||||
}
|
||||
{
|
||||
Object.keys(struct).map((key, index) => {
|
||||
normal.map((s, index) => {
|
||||
return (
|
||||
<div key={key+"-"+index}>
|
||||
{ this.section(struct[key], key, level + 1) }
|
||||
<div key={s.key+"-"+index}>
|
||||
{ this.section(s.data, s.key, level + 1) }
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
<div className="advanced_form">
|
||||
{
|
||||
advanced.map((s, index) => {
|
||||
return (
|
||||
<div key={s.key+"-"+index}>
|
||||
{ this.section(s.data, s.key, level + 1) }
|
||||
</div>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -78,6 +100,7 @@ export class FormBuilder extends React.Component {
|
||||
FormObjToJSON(this.props.form)
|
||||
);
|
||||
};
|
||||
|
||||
return ( <FormElement render={this.props.render} onChange={onChange.bind(this)} {...id} params={struct} target={target} name={ format(struct.label) } /> );
|
||||
}
|
||||
|
||||
@ -90,7 +113,7 @@ export class FormBuilder extends React.Component {
|
||||
const FormElement = (props) => {
|
||||
const id = props.id !== undefined ? {id: props.id} : {};
|
||||
let struct = props.params;
|
||||
let $input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={props.name} type="text" defaultValue={struct.value} placeholder={struct.placeholder} /> );
|
||||
let $input = ( <Input onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} type="text" defaultValue={struct.value} placeholder={struct.placeholder} /> );
|
||||
switch(props.params["type"]){
|
||||
case "text":
|
||||
const onTextChange = (value) => {
|
||||
@ -99,14 +122,14 @@ const FormElement = (props) => {
|
||||
}
|
||||
props.onChange(value);
|
||||
};
|
||||
$input = ( <Input onChange={(e) => onTextChange(e.target.value)} {...id} name={props.name} type="text" value={struct.value || ""} placeholder={struct.placeholder}/> );
|
||||
$input = ( <Input onChange={(e) => onTextChange(e.target.value)} {...id} name={struct.label} type="text" value={struct.value || ""} placeholder={struct.placeholder} readOnly={struct.readonly}/> );
|
||||
break;
|
||||
case "number":
|
||||
const onNumberChange = (value) => {
|
||||
value = value === "" ? null : parseInt(value);
|
||||
props.onChange(value);
|
||||
};
|
||||
$input = ( <Input onChange={(e) => onNumberChange(e.target.value)} {...id} name={props.name} type="number" value={struct.value || ""} placeholder={struct.placeholder} /> );
|
||||
$input = ( <Input onChange={(e) => onNumberChange(e.target.value)} {...id} name={struct.label} type="number" value={struct.value || ""} placeholder={struct.placeholder} /> );
|
||||
break;
|
||||
case "password":
|
||||
const onPasswordChange = (value) => {
|
||||
@ -115,56 +138,53 @@ const FormElement = (props) => {
|
||||
}
|
||||
props.onChange(value);
|
||||
};
|
||||
$input = ( <Input onChange={(e) => onPasswordChange(e.target.value)} {...id} name={props.name} type="password" value={struct.value || ""} placeholder={struct.placeholder} /> );
|
||||
$input = ( <Input onChange={(e) => onPasswordChange(e.target.value)} {...id} name={struct.label} type="password" value={struct.value || ""} placeholder={struct.placeholder} /> );
|
||||
break;
|
||||
case "long_password":
|
||||
const onLongPasswordChange = (value) => {
|
||||
if(value === ""){
|
||||
value = null;
|
||||
}
|
||||
props.onChange(value);
|
||||
};
|
||||
$input = (
|
||||
<Textarea {...id} disabledEnter={true} value={struct.value || ""} onChange={(e) => onLongPasswordChange(e.target.value)} type="text" rows="1" name={struct.label} placeholder={struct.placeholder} autoComplete="new-password" />
|
||||
);
|
||||
break;
|
||||
case "bcrypt":
|
||||
const onBcryptChange = (value) => {
|
||||
if(value === ""){
|
||||
return props.onChange(null);
|
||||
}
|
||||
bcrypt_password(value).then((hash) => {
|
||||
return bcrypt_password(value).then((hash) => {
|
||||
props.onChange(hash);
|
||||
});
|
||||
};
|
||||
$input = ( <Input onChange={(e) => onBcryptChange(e.target.value)} {...id} name={props.name} type="password" defaultValue={struct.value || ""} placeholder={struct.placeholder} /> );
|
||||
$input = ( <Input onChange={(e) => onBcryptChange(e.target.value)} {...id} name={struct.label} type="password" defaultValue={struct.value || ""} placeholder={struct.placeholder} /> );
|
||||
break;
|
||||
case "hidden":
|
||||
$input = ( <Input name={props.name} type="hidden" defaultValue={struct.value} /> );
|
||||
$input = ( <Input name={struct.label} type="hidden" defaultValue={struct.value} /> );
|
||||
break;
|
||||
case "boolean":
|
||||
$input = ( <Input onChange={(e) => props.onChange(e.target.checked)} {...id} name={props.name} type="checkbox" checked={struct.value === null ? !!struct.default : struct.value} /> );
|
||||
$input = ( <Input onChange={(e) => props.onChange(e.target.checked)} {...id} name={struct.label} type="checkbox" checked={struct.value === null ? !!struct.default : struct.value} /> );
|
||||
break;
|
||||
case "select":
|
||||
$input = ( <Select onChange={(e) => props.onChange(e.target.value)} {...id} name={props.name} choices={struct.options} value={struct.value === null ? struct.default : struct.value} placeholder={struct.placeholder} />);
|
||||
$input = ( <Select onChange={(e) => props.onChange(e.target.value)} {...id} name={struct.label} choices={struct.options} value={struct.value === null ? struct.default : struct.value} placeholder={struct.placeholder} />);
|
||||
break;
|
||||
case "enable":
|
||||
$input = ( <Enabler onChange={(e) => props.onChange(e.target.checked)} {...id} name={props.name} target={props.target} defaultValue={struct.value === null ? struct.default : struct.value} /> );
|
||||
$input = ( <Enabler onChange={(e) => props.onChange(e.target.checked)} {...id} name={struct.label} target={props.target} defaultValue={struct.value === null ? struct.default : struct.value} /> );
|
||||
break;
|
||||
case "image":
|
||||
$input = ( <img {...id} src={props.value} /> );
|
||||
$input = ( <img {...id} src={struct.value} /> );
|
||||
break;
|
||||
case "oauth2":
|
||||
$input = null;
|
||||
break;
|
||||
}
|
||||
|
||||
if(props.render){
|
||||
return props.render($input, props, struct, props.onChange.bind(null, null));
|
||||
}
|
||||
|
||||
return (
|
||||
<label className={"no-select input_type_" + props.params["type"]}>
|
||||
<div>
|
||||
<span>
|
||||
{ format(struct.label) }:
|
||||
</span>
|
||||
<div style={{width: '100%'}}>
|
||||
{ $input }
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="nothing"></span>
|
||||
<div style={{width: '100%'}}>
|
||||
{ struct.description ? (<div className="description">{struct.description}</div>) : null }
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
FormElement.PropTypes = {
|
||||
render: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
@ -9,8 +9,8 @@
|
||||
font-size: 0.95em;
|
||||
}
|
||||
|
||||
input::placeholder{
|
||||
opacity: 0.5;
|
||||
input::placeholder, textarea::placeholder{
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
label.input_type_hidden{
|
||||
@ -25,4 +25,9 @@
|
||||
padding: 0 15px;
|
||||
}
|
||||
}
|
||||
|
||||
img{
|
||||
max-height: 111px;
|
||||
border: 9px solid rgba(0,0,0,0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ export { Input, Select, Enabler } from './input';
|
||||
export { Textarea } from './textarea';
|
||||
export { Button } from './button';
|
||||
export { Container } from './container';
|
||||
export { NgIf } from './ngif';
|
||||
export { NgIf, NgShow } from './ngif';
|
||||
export { Card } from './card';
|
||||
export { Loader } from './loader';
|
||||
export { Fab } from './fab';
|
||||
|
||||
@ -13,8 +13,6 @@
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
line-height: 18px;
|
||||
|
||||
|
||||
border-bottom: 2px solid rgba(70, 99, 114, 0.1);
|
||||
transition: border-color 0.2s ease-out;
|
||||
&:focus{
|
||||
@ -22,6 +20,13 @@
|
||||
}
|
||||
}
|
||||
|
||||
input.component_input[readonly], textarea.component_textarea[readonly]{
|
||||
border-bottom-style: dashed;
|
||||
cursor: not-allowed;
|
||||
font-style: italic;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.component_select{
|
||||
background: inherit;
|
||||
border-radius: 0;
|
||||
|
||||
@ -27,3 +27,35 @@ NgIf.propTypes = {
|
||||
cond: PropTypes.bool.isRequired,
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
||||
|
||||
export class NgShow extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
let clean_prop = Object.assign({}, this.props);
|
||||
delete clean_prop.cond;
|
||||
delete clean_prop.children;
|
||||
delete clean_prop.type;
|
||||
if(this.props.cond){
|
||||
if(this.props.type === "inline"){
|
||||
return <span {...clean_prop}>{this.props.children}</span>;
|
||||
}else{
|
||||
return <div {...clean_prop}>{this.props.children}</div>;
|
||||
}
|
||||
}else{
|
||||
return (
|
||||
<div style={{display: "none"}}>
|
||||
{this.props.children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NgShow.propTypes = {
|
||||
cond: PropTypes.bool.isRequired,
|
||||
type: PropTypes.string
|
||||
};
|
||||
|
||||
@ -10,10 +10,14 @@ export class Textarea extends React.Component {
|
||||
|
||||
render() {
|
||||
let className = "component_textarea";
|
||||
if(/Firefox/.test(navigator.userAgent)){
|
||||
className += " firefox";
|
||||
}
|
||||
if((this.refs.el !== undefined && this.refs.el.value.length > 0) || (this.props.value !== undefined && this.props.value.length > 0)){
|
||||
className += " hasText";
|
||||
}
|
||||
|
||||
|
||||
const disabledEnter = (e) => {
|
||||
if(e.key === "Enter" && e.shiftKey === false){
|
||||
e.preventDefault();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
@font-face {
|
||||
font-family: password;
|
||||
src: url('textarea.woff');
|
||||
font-family: 'text-security-disc';
|
||||
src: url('textarea.woff') format('woff2');
|
||||
}
|
||||
|
||||
.component_textarea{
|
||||
@ -20,14 +20,22 @@
|
||||
vertical-align: top;
|
||||
min-width: 100%;
|
||||
max-width: 100%;
|
||||
max-height: 31px; /* Firefox was doing some weird things */
|
||||
min-height: 30px;
|
||||
max-height: 30px;
|
||||
|
||||
line-height: 18px;
|
||||
&[name="password"]{
|
||||
&.hasText{
|
||||
font-family: 'password';
|
||||
}
|
||||
resize: none;
|
||||
-webkit-text-security:disc!important;
|
||||
-webkit-touch-callout: none;
|
||||
-webkit-user-select: none;
|
||||
-khtml-user-select: none;
|
||||
-moz-user-select: moz-none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
&.firefox.hasText{
|
||||
font-family: 'text-security-disc';
|
||||
}
|
||||
}
|
||||
|
||||
border-bottom: 2px solid rgba(70, 99, 114, 0.1);
|
||||
|
||||
Binary file not shown.
@ -1,4 +1,4 @@
|
||||
export { URL_HOME, goToHome, URL_FILES, goToFiles, URL_VIEWER, goToViewer, URL_LOGIN, goToLogin, URL_LOGOUT, goToLogout } from './navigate';
|
||||
export { URL_HOME, goToHome, URL_FILES, goToFiles, URL_VIEWER, goToViewer, URL_LOGIN, goToLogin, URL_LOGOUT, goToLogout, urlParams } from './navigate';
|
||||
export { opener } from './mimetype';
|
||||
export { debounce, throttle } from './backpressure';
|
||||
export { encrypt, decrypt, bcrypt_password } from './crypto';
|
||||
|
||||
@ -40,3 +40,20 @@ function encode_path(path){
|
||||
export function prepare(path){
|
||||
return encodeURIComponent(decodeURIComponent(path)); // to send our url correctly without using directly '/'
|
||||
}
|
||||
|
||||
export function urlParams() {
|
||||
let p = "";
|
||||
if(window.location.hash){
|
||||
p += window.location.hash.replace(/^\#/, "");
|
||||
}
|
||||
if(window.location.search){
|
||||
if(p !== "") p += "&";
|
||||
p += window.location.search.replace(/^\?/, "");
|
||||
}
|
||||
return p.split("&").reduce((mem, chunk) => {
|
||||
const d = chunk.split("=");
|
||||
if(d.length !== 2) return mem;
|
||||
mem[decodeURIComponent(d[0])] = decodeURIComponent(d[1]);
|
||||
return mem;
|
||||
}, {})
|
||||
}
|
||||
|
||||
@ -7,22 +7,9 @@ class SessionManager{
|
||||
.then(data => data.result);
|
||||
}
|
||||
|
||||
url(type){
|
||||
if(type === 'dropbox'){
|
||||
let url = '/api/session/auth/dropbox';
|
||||
oauth2(url){
|
||||
return http_get(url)
|
||||
.then(data => data.result);
|
||||
}else if(type === 'gdrive'){
|
||||
let url = '/api/session/auth/gdrive';
|
||||
return http_get(url)
|
||||
.then(data => data.result);
|
||||
}else if(type === 'custombackend'){
|
||||
let url = '/api/session/auth/custombackend';
|
||||
return http_get(url)
|
||||
.then(data => data.result);
|
||||
}else{
|
||||
return Promise.reject({message: 'no authorization backend', code: 'UNKNOWN_PROVIDER'});
|
||||
}
|
||||
}
|
||||
|
||||
authenticate(params){
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { FormBuilder } from '../../components/';
|
||||
import { Config } from '../../model/';
|
||||
import { format } from '../../helpers';
|
||||
|
||||
export class ConfigPage extends React.Component {
|
||||
constructor(props){
|
||||
@ -32,7 +33,7 @@ export class ConfigPage extends React.Component {
|
||||
}
|
||||
|
||||
onChange(form){
|
||||
form.connections = window.CONFIG.connections
|
||||
form.connections = window.CONFIG.connections;
|
||||
Config.save(form);
|
||||
this.setState({refresh: Math.random()});
|
||||
}
|
||||
@ -40,7 +41,26 @@ export class ConfigPage extends React.Component {
|
||||
render(){
|
||||
return (
|
||||
<form className="sticky">
|
||||
<FormBuilder form={this.state.form} onChange={this.onChange.bind(this)} />
|
||||
<FormBuilder form={this.state.form} onChange={this.onChange.bind(this)} render={ ($input, props, struct, onChange) => {
|
||||
return (
|
||||
<label className={"no-select input_type_" + props.params["type"]}>
|
||||
<div>
|
||||
<span>
|
||||
{ format(struct.label) }:
|
||||
</span>
|
||||
<div style={{width: '100%'}}>
|
||||
{ $input }
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="nothing"></span>
|
||||
<div style={{width: '100%'}}>
|
||||
{ struct.description ? (<div className="description">{struct.description}</div>) : null }
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}}/>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ export class DashboardPage extends React.Component {
|
||||
let [backend, config] = data;
|
||||
this.setState({
|
||||
backend_available: backend,
|
||||
backend_enabled: window.CONFIG.connections.map((conn) => {
|
||||
backend_enabled: window.CONFIG["connections"].map((conn) => {
|
||||
return createFormBackend(backend, conn);
|
||||
}),
|
||||
config: config
|
||||
@ -126,6 +126,8 @@ export class DashboardPage extends React.Component {
|
||||
);
|
||||
if(struct.label === "label"){
|
||||
$checkbox = null;
|
||||
} else if(struct.readonly === true) {
|
||||
$checkbox = null;
|
||||
}
|
||||
return (
|
||||
<label className={"no-select input_type_" + props.params["type"]}>
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { FormBuilder, Loader, Button, Icon } from '../../components/';
|
||||
import { Config, Log } from '../../model/';
|
||||
import { FormObjToJSON, notify } from '../../helpers/';
|
||||
import { FormObjToJSON, notify, format } from '../../helpers/';
|
||||
|
||||
import "./logger.scss";
|
||||
|
||||
@ -48,12 +48,34 @@ export class LogPage extends React.Component {
|
||||
let tmp = "access_";
|
||||
tmp += new Date().toISOString().substring(0,10).replace(/-/g, "");
|
||||
tmp += ".log";
|
||||
}
|
||||
};
|
||||
return (
|
||||
<div className="component_logpage">
|
||||
<h2>Logging { this.state.loading === true ? <Icon style={{height: '40px'}} name="loading"/> : null}</h2>
|
||||
<div style={{minHeight: '150px'}}>
|
||||
<FormBuilder form={this.state.form} onChange={this.onChange.bind(this)} />
|
||||
<FormBuilder form={this.state.form} onChange={this.onChange.bind(this)}
|
||||
render={ ($input, props, struct, onChange) => {
|
||||
return (
|
||||
<label className={"no-select input_type_" + props.params["type"]}>
|
||||
<div>
|
||||
<span>
|
||||
{ format(struct.label) }:
|
||||
</span>
|
||||
<div style={{width: '100%'}}>
|
||||
{ $input }
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<span className="nothing"></span>
|
||||
<div style={{width: '100%'}}>
|
||||
{
|
||||
struct.description ? (<div className="description">{struct.description}</div>) : null
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}} />
|
||||
</div>
|
||||
|
||||
<pre style={{height: '350px'}} ref="$log">
|
||||
|
||||
@ -3,9 +3,9 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
|
||||
import './connectpage.scss';
|
||||
import { Session } from '../model/';
|
||||
import { Container, NgIf, Loader, Notification } from '../components/';
|
||||
import { Container, NgIf, NgShow, Loader, Notification } from '../components/';
|
||||
import { ForkMe, RememberMe, Credentials, Form } from './connectpage/';
|
||||
import { cache, notify } from '../helpers/';
|
||||
import { cache, notify, urlParams } from '../helpers/';
|
||||
|
||||
import { Alert } from '../components/';
|
||||
|
||||
@ -15,35 +15,27 @@ export class ConnectPage extends React.Component {
|
||||
this.state = {
|
||||
credentials: {},
|
||||
remember_me: window.localStorage.hasOwnProperty('credentials') ? true : false,
|
||||
loading: false,
|
||||
loading: true,
|
||||
doing_a_third_party_login: false
|
||||
};
|
||||
}
|
||||
|
||||
componentWillMount(){
|
||||
function getParam(name) {
|
||||
const regex = new RegExp("[?&#]" + name.replace(/[\[\]]/g, "\\$&") + "(=([^&#]*)|&|#|$)");
|
||||
const results = regex.exec(window.location.href);
|
||||
if (!results) return null;
|
||||
if (!results[2]) return '';
|
||||
return decodeURIComponent(results[2].replace(/\+/g, " "));
|
||||
const urlData = urlParams();
|
||||
if(Object.keys(urlData).length === 0){
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getParam('state');
|
||||
if(state === "dropbox"){
|
||||
this.setState({doing_a_third_party_login: true});
|
||||
this.authenticate({bearer: getParam('access_token'), type: 'dropbox'});
|
||||
}else if(state === "googledrive"){
|
||||
this.setState({doing_a_third_party_login: true});
|
||||
this.authenticate({code: getParam('code'), type: 'gdrive'});
|
||||
}else if(state === "custombackend"){
|
||||
this.setState({doing_a_third_party_login: true});
|
||||
this.authenticate({code: getParam('code'), type: 'custombackend'});
|
||||
if(!urlData.type){
|
||||
urlData.type = urlData.state;
|
||||
}
|
||||
this.setState({
|
||||
doing_a_third_party_login: true,
|
||||
loading: true
|
||||
}, () => this.authenticate(urlData));
|
||||
}
|
||||
|
||||
authenticate(params){
|
||||
this.setState({loading: true});
|
||||
Session.authenticate(params)
|
||||
.then(Session.currentUser)
|
||||
.then((user) => {
|
||||
@ -64,37 +56,18 @@ export class ConnectPage extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
initiateAuthToThirdParty(source){
|
||||
if(source === 'dropbox'){
|
||||
this.setState({loading: true});
|
||||
Session.url('dropbox').then((url) => {
|
||||
window.location.href = url;
|
||||
}).catch((err) => {
|
||||
this.setState({loading: false});
|
||||
notify.send(err, 'error');
|
||||
});
|
||||
}else if(source === 'google'){
|
||||
this.setState({loading: true});
|
||||
Session.url('gdrive').then((url) => {
|
||||
window.location.href = url;
|
||||
}).catch((err) => {
|
||||
this.setState({loading: false});
|
||||
notify.send(err, 'error');
|
||||
});
|
||||
}else if(source === 'custombackend'){
|
||||
Session.url('custombackend').then((url) => {
|
||||
window.location.href = url;
|
||||
}).catch((err) => {
|
||||
this.setState({loading: false});
|
||||
notify.send(err, 'error');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onFormSubmit(data, credentials){
|
||||
this.setState({credentials: credentials}, () => {
|
||||
this.authenticate(data);
|
||||
if('oauth2' in data){
|
||||
this.setState({loading: true});
|
||||
Session.oauth2(data.oauth2).then((url) => {
|
||||
window.location.href = url;
|
||||
});
|
||||
return;
|
||||
}
|
||||
this.setState({
|
||||
credentials: credentials,
|
||||
loading: true
|
||||
}, () => this.authenticate(data));
|
||||
}
|
||||
|
||||
setRemember(state){
|
||||
@ -105,6 +78,12 @@ export class ConnectPage extends React.Component {
|
||||
this.setState({credentials: creds});
|
||||
}
|
||||
|
||||
setLoading(value){
|
||||
if(this.state.doing_a_third_party_login !== true){
|
||||
this.setState({loading: value});
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="component_page_connect">
|
||||
@ -115,16 +94,16 @@ export class ConnectPage extends React.Component {
|
||||
<NgIf cond={this.state.loading === true}>
|
||||
<Loader/>
|
||||
</NgIf>
|
||||
<NgIf cond={this.state.loading === false}>
|
||||
<NgShow cond={this.state.loading === false}>
|
||||
<ReactCSSTransitionGroup transitionName="form" transitionLeave={false} transitionEnter={false} transitionAppear={true} transitionAppearTimeout={500}>
|
||||
<Form credentials={this.state.credentials}
|
||||
onThirdPartyLogin={this.initiateAuthToThirdParty.bind(this)}
|
||||
onLoadingChange={this.setLoading.bind(this)}
|
||||
onSubmit={this.onFormSubmit.bind(this)} />
|
||||
</ReactCSSTransitionGroup>
|
||||
<ReactCSSTransitionGroup transitionName="remember" transitionLeave={false} transitionEnter={false} transitionAppear={true} transitionAppearTimeout={5000}>
|
||||
<RememberMe state={this.state.remember_me} onChange={this.setRemember.bind(this)}/>
|
||||
</ReactCSSTransitionGroup>
|
||||
</NgIf>
|
||||
</NgShow>
|
||||
<NgIf cond={this.state.doing_a_third_party_login === false}>
|
||||
<Credentials remember_me={this.state.remember_me}
|
||||
onRememberMeChange={this.setRemember.bind(this)}
|
||||
|
||||
@ -5,13 +5,14 @@ import ReactCSSTransitionGroup from 'react-addons-css-transition-group';
|
||||
import { ModalPrompt } from '../../components/';
|
||||
import { encrypt, decrypt, memory, prompt } from '../../helpers/';
|
||||
|
||||
const CREDENTIALS_CACHE = "credentials",
|
||||
CREDENTIALS_KEY = "credentials_key";
|
||||
|
||||
export class Credentials extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
const key = memory.get('credentials_key') || ''; // we use a clojure for the "key" because we
|
||||
// want to persist it in memory, not just in the
|
||||
// state which is kill whenever react decide
|
||||
|
||||
const key = memory.get(CREDENTIALS_KEY) || ''; // we use a clojure for the "key" because we require control
|
||||
// without the influence of the react component lifecycle
|
||||
this.state = {
|
||||
key: key || '',
|
||||
message: '',
|
||||
@ -20,7 +21,7 @@ export class Credentials extends React.Component {
|
||||
|
||||
if(this.props.remember_me === true){
|
||||
if(key === ""){
|
||||
let raw = window.localStorage.hasOwnProperty('credentials');
|
||||
let raw = window.localStorage.hasOwnProperty(CREDENTIALS_CACHE);
|
||||
if(raw){
|
||||
this.promptForExistingPassword();
|
||||
}else{
|
||||
@ -33,16 +34,19 @@ export class Credentials extends React.Component {
|
||||
}
|
||||
|
||||
componentWillReceiveProps(new_props){
|
||||
if(new_props.remember_me === false){
|
||||
if(window.CONFIG["remember_me"] === false){
|
||||
window.localStorage.clear();
|
||||
}else if(new_props.remember_me === true){
|
||||
return;
|
||||
} else if(new_props.remember_me === false){
|
||||
window.localStorage.clear();
|
||||
} else if(new_props.remember_me === true){
|
||||
this.saveCreds(new_props.credentials);
|
||||
}
|
||||
|
||||
if(new_props.remember_me === true && this.props.remember_me === false){
|
||||
this.promptForNewPassword();
|
||||
}else if(new_props.remember_me === false && this.props.remember_me === true){
|
||||
memory.set('credentials_key', '');
|
||||
memory.set(CREDENTIALS_KEY, '');
|
||||
this.setState({key: ''});
|
||||
}
|
||||
}
|
||||
@ -53,11 +57,11 @@ export class Credentials extends React.Component {
|
||||
(key) => {
|
||||
if(!key.trim()) return Promise.reject("Password can\'t be empty");
|
||||
this.setState({key: key});
|
||||
memory.set('credentials_key', key);
|
||||
memory.set(CREDENTIALS_KEY, key);
|
||||
return this.hidrate_credentials(key);
|
||||
},
|
||||
() => {
|
||||
memory.set('credentials_key', '');
|
||||
memory.set(CREDENTIALS_KEY, '');
|
||||
this.setState({key: ''});
|
||||
},
|
||||
'password'
|
||||
@ -68,7 +72,7 @@ export class Credentials extends React.Component {
|
||||
"Pick a Master Password",
|
||||
(key) => {
|
||||
if(!key.trim()) return Promise.reject("Password can\'t be empty");
|
||||
memory.set('credentials_key', key);
|
||||
memory.set(CREDENTIALS_KEY, key);
|
||||
this.setState({key: key}, () => {
|
||||
this.saveCreds(this.props.credentials);
|
||||
});
|
||||
@ -80,7 +84,7 @@ export class Credentials extends React.Component {
|
||||
}
|
||||
|
||||
hidrate_credentials(key){
|
||||
let raw = window.localStorage.getItem('credentials');
|
||||
let raw = window.localStorage.getItem(CREDENTIALS_CACHE);
|
||||
if(raw){
|
||||
try{
|
||||
let credentials = decrypt(raw, key);
|
||||
@ -96,9 +100,9 @@ export class Credentials extends React.Component {
|
||||
}
|
||||
|
||||
saveCreds(creds){
|
||||
const key = memory.get('credentials_key');
|
||||
const key = memory.get(CREDENTIALS_KEY);
|
||||
if(key){
|
||||
window.localStorage.setItem('credentials', encrypt(creds, key));
|
||||
window.localStorage.setItem(CREDENTIALS_CACHE, encrypt(creds, key));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from "react";
|
||||
import { Container, Card, NgIf, Input, Button, Textarea, FormBuilder } from "../../components/";
|
||||
import { gid, settings_get, settings_put, createFormBackend } from "../../helpers/";
|
||||
import { gid, settings_get, settings_put, createFormBackend, FormObjToJSON } from "../../helpers/";
|
||||
import { Session, Backend } from "../../model/";
|
||||
import "./form.scss";
|
||||
import img_drive from "../../assets/img/google-drive.png";
|
||||
@ -10,25 +10,34 @@ export class Form extends React.Component {
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
select: window.CONFIG["connections"].length > 2 ? 2 : 0,
|
||||
backends: JSON.parse(JSON.stringify(window.CONFIG["connections"])).map((backend) => {
|
||||
return backend;
|
||||
}),
|
||||
dummy: null
|
||||
select: function(){
|
||||
const connLength = window.CONFIG["connections"].length;
|
||||
if(connLength < 4) return 0;
|
||||
else if(connLength < 5) return 1;
|
||||
return 2;
|
||||
}(),
|
||||
backends_enabled: []
|
||||
};
|
||||
|
||||
const select = settings_get("login_tab");
|
||||
if(select !== null && select < window.CONFIG["connections"].length){ this.state.select = select; }
|
||||
this.rerender = this.rerender.bind(this);
|
||||
this.rerender = () => this.setState({_refresh: !this.state._refresh});
|
||||
}
|
||||
|
||||
componentDidMount(){
|
||||
window.addEventListener("resize", this.rerender);
|
||||
this.publishState(this.props);
|
||||
Backend.all().then((b) => {
|
||||
Backend.all().then((backend) => {
|
||||
this.props.onLoadingChange(false);
|
||||
this.setState({
|
||||
backend_available: b
|
||||
backends_available: backend,
|
||||
backends_enabled: window.CONFIG["connections"].map((conn) => {
|
||||
return createFormBackend(backend, conn);
|
||||
})
|
||||
}, () => {
|
||||
return this.publishState(this.props);
|
||||
});
|
||||
}).catch((err) => {
|
||||
this.props.error(err);
|
||||
});
|
||||
}
|
||||
|
||||
@ -45,96 +54,40 @@ export class Form extends React.Component {
|
||||
|
||||
publishState(props){
|
||||
for(let key in props.credentials){
|
||||
this.state.backends = this.state.backends.map((backend) => {
|
||||
if(backend["type"] + "_" + backend["label"] === key){
|
||||
backend = props.credentials[key];
|
||||
this.state.backends_enabled = this.state.backends_enabled.map((backend) => {
|
||||
const b = backend[Object.keys(backend)[0]];
|
||||
if(b["type"].value + "_" + b["label"].value === key){
|
||||
for(let k in b){
|
||||
if(props.credentials[key][k]){
|
||||
b[k].value = props.credentials[key][k];
|
||||
}
|
||||
}
|
||||
}
|
||||
return backend;
|
||||
});
|
||||
}
|
||||
this.setState({backends: this.state.backends});
|
||||
this.setState({backends_enabled: this.state.backends_enabled});
|
||||
}
|
||||
|
||||
onSubmit(e){
|
||||
e.preventDefault();
|
||||
const authData = this.state.backends[this.state.select],
|
||||
key = authData["type"]+"_"+authData["label"];
|
||||
|
||||
this.props.credentials[key] = authData;
|
||||
this.props.onSubmit(authData, this.props.credentials);
|
||||
}
|
||||
|
||||
onFormUpdate(n, values){
|
||||
this.state.backends[n] = values;
|
||||
this.setState({backends: this.state.backends});
|
||||
}
|
||||
|
||||
redirect(service){
|
||||
this.props.onThirdPartyLogin(service);
|
||||
const data = () => {
|
||||
const tmp = this.state.backends_enabled[this.state.select];
|
||||
return tmp[Object.keys(tmp)[0]];
|
||||
};
|
||||
const dataToBeSubmitted = JSON.parse(JSON.stringify(FormObjToJSON(data())));
|
||||
const key = dataToBeSubmitted["type"] + "_" + dataToBeSubmitted["label"];
|
||||
delete dataToBeSubmitted.image;
|
||||
delete dataToBeSubmitted.label;
|
||||
delete dataToBeSubmitted.advanced;
|
||||
this.props.credentials[key] = dataToBeSubmitted;
|
||||
this.props.onSubmit(dataToBeSubmitted, this.props.credentials);
|
||||
}
|
||||
|
||||
onTypeChange(n){
|
||||
this.setState({select: n});
|
||||
}
|
||||
|
||||
rerender(){
|
||||
this.setState({_refresh: !this.state._refresh});
|
||||
}
|
||||
|
||||
render2() {
|
||||
const _marginTop = () => {
|
||||
let size = 300;
|
||||
const $screen = document.querySelector(".login-form");
|
||||
if($screen) size = $screen.offsetHeight;
|
||||
|
||||
size = Math.round((document.body.offsetHeight - size) / 2);
|
||||
if(size < 0) return 0;
|
||||
if(size > 150) return 150;
|
||||
return size;
|
||||
};
|
||||
return (
|
||||
<Card style={{marginTop: _marginTop()+"px"}} className="no-select component_page_connection_form">
|
||||
<NgIf cond={ window.CONFIG["connections"].length > 1 }>
|
||||
<div className={"buttons "+((window.innerWidth < 600) ? "scroll-x" : "")}>
|
||||
{
|
||||
this.state.backends.map((backend, i) => {
|
||||
return (
|
||||
<Button key={"menu-"+i} className={i === this.state.select ? "active primary" : ""} onClick={this.onTypeChange.bind(this, i)}>
|
||||
{backend.label}
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
}
|
||||
</div>
|
||||
</NgIf>
|
||||
<div>
|
||||
<form onSubmit={this.onSubmit.bind(this)} autoComplete="off" autoCapitalize="off" spellCheck="false" autoCorrect="off">
|
||||
{
|
||||
this.state.backends.map((conn, i) => {
|
||||
console.log(this.state.backend_available);
|
||||
return (
|
||||
<NgIf key={"form-"+i} cond={this.state.select === i}>
|
||||
<FormBuilder form={{"": createFormBackend(this.state.backend_available, conn)}} onChange={() => {this.setState({refresh: !this.state.refresh})}}
|
||||
render={ ($input, props, struct, onChange) => {
|
||||
return (
|
||||
<div style={{width: '100%'}}>
|
||||
{ $input }
|
||||
</div>
|
||||
);
|
||||
}}/>
|
||||
</NgIf>
|
||||
);
|
||||
})
|
||||
}
|
||||
<Button theme="emphasis">CONNECT</Button>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
// <FormBuilder onChange={() => {}} form={{"": conn}}/>
|
||||
|
||||
|
||||
render() {
|
||||
const _marginTop = () => {
|
||||
let size = 300;
|
||||
@ -146,16 +99,17 @@ export class Form extends React.Component {
|
||||
if(size > 150) return 150;
|
||||
return size;
|
||||
};
|
||||
let className = (window.innerWidth < 600) ? "scroll-x" : "";
|
||||
|
||||
return (
|
||||
<Card style={{marginTop: _marginTop()+"px"}} className="no-select component_page_connection_form">
|
||||
<NgIf cond={ CONFIG["connections"].length > 1 }>
|
||||
<NgIf cond={ window.CONFIG["connections"].length > 1 }>
|
||||
<div className={"buttons "+((window.innerWidth < 600) ? "scroll-x" : "")}>
|
||||
{
|
||||
this.state.backends.map((backend, i) => {
|
||||
this.state.backends_enabled.map((backend, i) => {
|
||||
const key = Object.keys(backend)[0];
|
||||
return (
|
||||
<Button key={"menu-"+i} className={i === this.state.select ? "active primary" : ""} onClick={this.onTypeChange.bind(this, i)}>
|
||||
{backend.label}
|
||||
{ backend[key].label.value }
|
||||
</Button>
|
||||
);
|
||||
})
|
||||
@ -165,415 +119,42 @@ export class Form extends React.Component {
|
||||
<div>
|
||||
<form onSubmit={this.onSubmit.bind(this)} autoComplete="off" autoCapitalize="off" spellCheck="false" autoCorrect="off">
|
||||
{
|
||||
this.state.backends.map((conn, i) => {
|
||||
this.state.backends_enabled.map((form, i) => {
|
||||
const key = Object.keys(form)[0];
|
||||
if(!form[key]) return null;
|
||||
else if(this.state.select !== i) return null;
|
||||
return (
|
||||
<NgIf key={"form-"+i} cond={this.state.select === i}>
|
||||
<NgIf cond={conn.type === "webdav"}>
|
||||
<WebDavForm values={conn} config={CONFIG["connections"][i]} onChange={this.onFormUpdate.bind(this, i)} />
|
||||
</NgIf>
|
||||
<NgIf cond={conn.type === "ftp"}>
|
||||
<FtpForm values={conn} config={CONFIG["connections"][i]} onChange={this.onFormUpdate.bind(this, i)} />
|
||||
</NgIf>
|
||||
<NgIf cond={conn.type === "sftp"}>
|
||||
<SftpForm values={conn} config={CONFIG["connections"][i]} onChange={this.onFormUpdate.bind(this, i)} />
|
||||
</NgIf>
|
||||
<NgIf cond={conn.type === "git"}>
|
||||
<GitForm values={conn} config={CONFIG["connections"][i]} onChange={this.onFormUpdate.bind(this, i)} />
|
||||
</NgIf>
|
||||
<NgIf cond={conn.type === "s3"}>
|
||||
<S3Form values={conn} config={CONFIG["connections"][i]} onChange={this.onFormUpdate.bind(this, i)} />
|
||||
</NgIf>
|
||||
<NgIf cond={conn.type === "dropbox"} className="third-party">
|
||||
<DropboxForm values={conn} config={CONFIG["connections"][i]} onThirdPartyLogin={this.redirect.bind(this)} />
|
||||
</NgIf>
|
||||
<NgIf cond={conn.type === "gdrive"} className="third-party">
|
||||
<GDriveForm values={conn} config={CONFIG["connections"][i]} onThirdPartyLogin={this.redirect.bind(this)} />
|
||||
</NgIf>
|
||||
<NgIf cond={conn.type === "custombackend"} className="third-party">
|
||||
<CustomForm values={conn} config={CONFIG["connections"][i]} onThirdPartyLogin={this.redirect.bind(this)} />
|
||||
</NgIf>
|
||||
</NgIf>
|
||||
<FormBuilder form={form[key]} onChange={this.rerender.bind(this)} key={"form"+i}
|
||||
render={ ($input, props, struct, onChange) => {
|
||||
if(struct.type === "image"){
|
||||
return (
|
||||
<div className="center">
|
||||
{ $input }
|
||||
</div>
|
||||
);
|
||||
} else if(struct.enabled === true){
|
||||
return null;
|
||||
} else if(struct.label === "advanced") return (
|
||||
<label style={{color: "rgba(0,0,0,0.4)"}}>
|
||||
{ $input }
|
||||
advanced
|
||||
</label>
|
||||
);
|
||||
return (
|
||||
<label className={"no-select input_type_" + props.params["type"]}>
|
||||
<div>
|
||||
{ $input }
|
||||
</div>
|
||||
</label>
|
||||
);
|
||||
}} />
|
||||
);
|
||||
})
|
||||
}
|
||||
<Button theme="emphasis">CONNECT</Button>
|
||||
</form>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const WebDavForm = formHelper(function(props){
|
||||
const onAdvanced = (value) => {
|
||||
if(value === true){
|
||||
props.values.path = "";
|
||||
}else{
|
||||
delete props.values.path;
|
||||
}
|
||||
props.onChange(props.values);
|
||||
};
|
||||
const is_advanced = props.advanced(props.values.path);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NgIf cond={props.should_appear("url")}>
|
||||
<Input value={props.values["url"] || ""} onChange={(e) => props.onChange("url", e.target.value)} type={props.input_type("url")} name="url" placeholder="Address*" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("username")}>
|
||||
<Input value={props.values["username"] || ""} onChange={(e) => props.onChange("username", e.target.value)} type={props.input_type("username")} name="username" placeholder="Username" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("password")}>
|
||||
<Input value={props.values["password"] || ""} onChange={(e) => props.onChange("password", e.target.value)} type={props.input_type("password")} name="password" placeholder="Password" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("advanced")}>
|
||||
<label>
|
||||
<input checked={is_advanced} onChange={(e) => onAdvanced(e.target.checked)} type="checkbox" autoComplete="new-password"/> Advanced
|
||||
</label>
|
||||
</NgIf>
|
||||
<NgIf cond={is_advanced} className="advanced_form">
|
||||
<NgIf cond={props.should_appear("path")}>
|
||||
<Input value={props.values["path"] || ""} onChange={(e) => props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
</NgIf>
|
||||
<Button theme="emphasis">CONNECT</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const FtpForm = formHelper(function(props){
|
||||
const onAdvanced = (value) => {
|
||||
if(value === true){
|
||||
props.values.path = "";
|
||||
props.values.port = "";
|
||||
}else{
|
||||
delete props.values.path;
|
||||
delete props.values.port;
|
||||
}
|
||||
props.onChange(props.values);
|
||||
};
|
||||
const is_advanced = props.advanced(
|
||||
props.values.path,
|
||||
props.values.port
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NgIf cond={props.should_appear("hostname")}>
|
||||
<Input value={props.values["hostname"] || ""} onChange={(e) => props.onChange("hostname", e.target.value)} type={props.input_type("hostname")} name="hostname" placeholder="Hostname*" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("username")}>
|
||||
<Input value={props.values["username"] || ""} onChange={(e) => props.onChange("username", e.target.value)} type={props.input_type("username")} name="username" placeholder="Username" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("password")}>
|
||||
<Input value={props.values["password"] || ""} onChange={(e) => props.onChange("password", e.target.value)} type={props.input_type("password")} name="password" placeholder="Password" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("advanced")}>
|
||||
<label>
|
||||
<input checked={is_advanced} onChange={e => onAdvanced(e.target.checked)} type="checkbox" autoComplete="new-password"/> Advanced
|
||||
</label>
|
||||
</NgIf>
|
||||
<NgIf cond={is_advanced} className="advanced_form">
|
||||
<NgIf cond={props.should_appear("path")}>
|
||||
<Input value={props.values["path"] || ""} onChange={(e) => props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("port")}>
|
||||
<Input value={props.values["port"] || ""} onChange={(e) => props.onChange("port", e.target.value)} type={props.input_type("port")} name="port" placeholder="Port" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("conn")}>
|
||||
<Input value={props.values["conn"] || ""} onChange={(e) => props.onChange("conn", e.target.value)} type={props.input_type("conn")} name="conn" placeholder="Number of connections" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
</NgIf>
|
||||
<Button type="submit" theme="emphasis">CONNECT</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const SftpForm = formHelper(function(props){
|
||||
const onAdvanced = (value) => {
|
||||
if(value === true){
|
||||
props.values.path = "";
|
||||
props.values.port = "";
|
||||
props.values.passphrase = "";
|
||||
}else{
|
||||
delete props.values.path;
|
||||
delete props.values.port;
|
||||
delete props.values.passphrase;
|
||||
}
|
||||
props.onChange();
|
||||
};
|
||||
const is_advanced = props.advanced(
|
||||
props.values.path,
|
||||
props.values.port,
|
||||
props.values.passphrase
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NgIf cond={props.should_appear("hostname")}>
|
||||
<Input value={props.values["hostname"] || ""} onChange={(e) => props.onChange("hostname", e.target.value)} value={props.values.hostname || ""} onChange={(e) => props.onChange("hostname", e.target.value)} type={props.input_type("hostname")} name="host" placeholder="Hostname*" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("username")}>
|
||||
<Input value={props.values["username"] || ""} onChange={(e) => props.onChange("username", e.target.value)} type={props.input_type("username")} name="username" placeholder="Username" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("password")}>
|
||||
<Textarea disabledEnter={true} value={props.values["password"] || ""} onChange={(e) => props.onChange("password", e.target.value)} type="text" style={props.input_type("password") === "hidden" ? {visibility: "hidden", position: "absolute"} : {} } rows="1" name="password" placeholder="Password" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("advanced")}>
|
||||
<label>
|
||||
<input checked={is_advanced} onChange={e => onAdvanced(e.target.checked)} type="checkbox" autoComplete="new-password"/> Advanced
|
||||
</label>
|
||||
</NgIf>
|
||||
<NgIf cond={is_advanced} className="advanced_form">
|
||||
<NgIf cond={props.should_appear("path")}>
|
||||
<Input value={props.values["path"] || ""} onChange={(e) => props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("port")}>
|
||||
<Input value={props.values["port"] || ""} onChange={(e) => props.onChange("port", e.target.value)} type={props.input_type("port")} name="port" placeholder="Port" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("passphrase")}>
|
||||
<Input value={props.values["passphrase"] || ""} onChange={(e) => props.onChange("passphrase", e.target.value)} type={props.input_type("passphrase")} name="port" placeholder="Passphrase" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
</NgIf>
|
||||
<Button type="submit" theme="emphasis">CONNECT</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
const GitForm = formHelper(function(props){
|
||||
const onAdvanced = (value) => {
|
||||
if(value === true){
|
||||
props.values.path = "";
|
||||
props.values.passphrase = "";
|
||||
props.values.commit = "";
|
||||
props.values.branch = "";
|
||||
props.values.author_email = "";
|
||||
props.values.author_name = "";
|
||||
props.values.committer_email = "";
|
||||
props.values.committer_name = "";
|
||||
}else{
|
||||
delete props.values.path;
|
||||
delete props.values.passphrase;
|
||||
delete props.values.commit;
|
||||
delete props.values.branch;
|
||||
delete props.values.author_email;
|
||||
delete props.values.author_name;
|
||||
delete props.values.committer_email;
|
||||
delete props.values.committer_name;
|
||||
}
|
||||
props.onChange();
|
||||
};
|
||||
const is_advanced = props.advanced(
|
||||
props.values.path,
|
||||
props.values.passphrase,
|
||||
props.values.commit,
|
||||
props.values.branch,
|
||||
props.values.author_email,
|
||||
props.values.author_name,
|
||||
props.values.committer_email,
|
||||
props.values.committer_name
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NgIf cond={props.should_appear("repo")}>
|
||||
<Input value={props.values["repo"] || ""} onChange={(e) => props.onChange("repo", e.target.value)} type={props.input_type("repo")} name="repo" placeholder="Repository*" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("username")}>
|
||||
<Input value={props.values["username"] || ""} onChange={(e) => props.onChange("username", e.target.value)} type={props.input_type("username")} name="username" placeholder="Username" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("password")}>
|
||||
<Textarea disabledEnter={true} value={props.values["password"] || ""} onChange={(e) => props.onChange("password", e.target.value)} type="text" style={props.input_type("password") === "hidden" ? {visibility: "hidden", position: "absolute"} : {} } rows="1" name="password" placeholder="Password" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<Input name="uid" value={gid()} type="hidden" />
|
||||
<NgIf cond={props.should_appear("advanced")}>
|
||||
<label>
|
||||
<input checked={is_advanced} onChange={(e) => onAdvanced(e.target.checked)} type="checkbox" autoComplete="new-password"/> Advanced
|
||||
</label>
|
||||
</NgIf>
|
||||
<NgIf cond={is_advanced} className="advanced_form">
|
||||
<NgIf cond={props.should_appear("path")}>
|
||||
<Input value={props.values["path"] || ""} onChange={(e) => props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("passphrase")}>
|
||||
<Input value={props.values["passphrase"] || ""} onChange={(e) => props.onChange("passphrase", e.target.value)} type={props.input_type("passphrase")} name="passphrase" placeholder="Passphrase" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("commit")}>
|
||||
<Input value={props.values["commit"] || ""} onChange={(e) => props.onChange("commit", e.target.value)} type={props.input_type("commit")} name="commit" placeholder='Commit Format: default to \"{action}({filename}): {path}\"' autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("branch")}>
|
||||
<Input value={props.values["branch"] || ""} onChange={(e) => props.onChange("branch", e.target.value)} type={props.input_type("branch")} name="branch" placeholder='Branch: default to "master"' autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("author_email")}>
|
||||
<Input value={props.values["author_email"] || ""} onChange={(e) => props.onChange("author_email", e.target.value)} type={props.input_type("author_email")} name="author_email" placeholder="Author email" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("author_name")}>
|
||||
<Input value={props.values["author_name"] || ""} onChange={(e) => props.onChange("author_name", e.target.value)} type={props.input_type("author_name")} name="author_name" placeholder="Author name" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("committer_email")}>
|
||||
<Input value={props.values["committer_email"] || ""} onChange={(e) => props.onChange("committer_email", e.target.value)} type={props.input_type("committer_email")} name="committer_email" placeholder="Committer email" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("committer_name")}>
|
||||
<Input value={props.values["committer_name"] || ""} onChange={(e) => props.onChange("committer_name", e.target.value)} type={props.input_type("committer_name")} name="committer_name" placeholder="Committer name" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
</NgIf>
|
||||
<Button type="submit" theme="emphasis">CONNECT</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const S3Form = formHelper(function(props){
|
||||
const onAdvanced = (value) => {
|
||||
if(value === true){
|
||||
props.values.path = "";
|
||||
props.values.endpoint = "";
|
||||
props.values.region = "";
|
||||
props.values.encryption_key = "";
|
||||
}else{
|
||||
delete props.values.path;
|
||||
delete props.values.endpoint;
|
||||
delete props.values.region;
|
||||
delete props.values.encryption_key;
|
||||
}
|
||||
props.onChange();
|
||||
};
|
||||
const is_advanced = props.advanced(
|
||||
props.values.path,
|
||||
props.values.endpoint,
|
||||
props.values.region,
|
||||
props.values.encryption_key
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<NgIf cond={props.should_appear("access_key_id")}>
|
||||
<Input value={props.values["access_key_id"] || ""} onChange={(e) => props.onChange("access_key_id", e.target.value)} value={props.values.access_key_id || ""} onChange={(e) => props.onChange("access_key_id", e.target.value)} type={props.input_type("access_key_id")} name="access_key_id" placeholder="Access Key ID*" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("secret_access_key")}>
|
||||
<Input value={props.values["secret_access_key"] || ""} onChange={(e) => props.onChange("secret_access_key", e.target.value)} type={props.input_type("secret_access_key")} name="secret_access_key" placeholder="Secret Access Key*" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("advanced")}>
|
||||
<label>
|
||||
<input checked={is_advanced} onChange={(e) => onAdvanced(e.target.checked)} type="checkbox" autoComplete="new-password"/> Advanced
|
||||
</label>
|
||||
</NgIf>
|
||||
<NgIf cond={is_advanced} className="advanced_form">
|
||||
<NgIf cond={props.should_appear("path")}>
|
||||
<Input value={props.values["path"] || ""} onChange={(e) => props.onChange("path", e.target.value)} type={props.input_type("path")} name="path" placeholder="Path" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("encryption_key")}>
|
||||
<Input value={props.values["encryption_key"] || ""} onChange={(e) => props.onChange("encryption_key", e.target.value)} type={props.input_type("encryption_key")} name="encryption_key" placeholder="Encryption Key" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("region")}>
|
||||
<Input value={props.values["region"] || ""} onChange={(e) => props.onChange("region", e.target.value)} type={props.input_type("region")} name="region" placeholder="Region" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
<NgIf cond={props.should_appear("endpoint")}>
|
||||
<Input value={props.values["endpoint"] || ""} onChange={(e) => props.onChange("endpoint", e.target.value)} type={props.input_type("endpoint")} name="endpoint" placeholder="Endpoint" autoComplete="new-password" />
|
||||
</NgIf>
|
||||
</NgIf>
|
||||
<Button type="submit" theme="emphasis">CONNECT</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const DropboxForm = formHelper(function(props){
|
||||
const redirect = () => {
|
||||
return props.onThirdPartyLogin("dropbox");
|
||||
};
|
||||
|
||||
if(CONFIG.connections.length === 1 && CONFIG.auto_connect === true){
|
||||
redirect();
|
||||
return (
|
||||
<div>
|
||||
AUTHENTICATING ...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div onClick={redirect}>
|
||||
<img src={img_dropbox} />
|
||||
</div>
|
||||
<Button type="button" onClick={redirect} theme="emphasis">LOGIN WITH DROPBOX</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const GDriveForm = formHelper(function(props){
|
||||
const redirect = () => {
|
||||
return props.onThirdPartyLogin("google");
|
||||
};
|
||||
|
||||
if(CONFIG.connections.length === 1 && CONFIG.auto_connect === true){
|
||||
redirect();
|
||||
return (
|
||||
<div>
|
||||
AUTHENTICATING ...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<div onClick={redirect}>
|
||||
<img src={img_drive}/>
|
||||
</div>
|
||||
<Button type="button" onClick={redirect} theme="emphasis">LOGIN WITH GOOGLE</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
const CustomForm = formHelper(function(props){
|
||||
const redirect = () => {
|
||||
return props.onThirdPartyLogin("custombackend");
|
||||
};
|
||||
|
||||
if(CONFIG.connections.length === 1 && CONFIG.auto_connect === true){
|
||||
redirect();
|
||||
return (
|
||||
<div>
|
||||
AUTHENTICATING ...
|
||||
</div>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<Button type="button" onClick={redirect} theme="emphasis">LOGIN</Button>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
function formHelper(WrappedComponent){
|
||||
return (props) => {
|
||||
const helpers = {
|
||||
should_appear: function(key){
|
||||
const val = props.config[key];
|
||||
if(val === undefined) return true;
|
||||
return false;
|
||||
},
|
||||
input_type: function(key){
|
||||
if(["password", "passphrase", "secret_access_key"].indexOf(key) !== -1){
|
||||
return "password";
|
||||
}
|
||||
return "text";
|
||||
},
|
||||
onChange: function(key, value){
|
||||
let values = props.values;
|
||||
if(typeof key === "string") values[key] = value;
|
||||
props.onChange(values);
|
||||
},
|
||||
advanced: function(){
|
||||
let res = false;
|
||||
for (let i=0; i < arguments.length; i++){
|
||||
if(arguments[i] !== undefined) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
};
|
||||
return (
|
||||
<WrappedComponent {...props} {...helpers} />
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
@ -19,22 +19,18 @@
|
||||
}
|
||||
}
|
||||
form{
|
||||
.formbuilder{
|
||||
fieldset{ padding: 0; border: none; }
|
||||
legend{ display: none; }
|
||||
}
|
||||
|
||||
padding-top: 5px;
|
||||
label{
|
||||
color: rgba(0,0,0,0.4);
|
||||
font-style: italic;
|
||||
font-size: 0.9em;
|
||||
display: block;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.advanced_form{
|
||||
max-height: 156px;
|
||||
overflow-y: auto;
|
||||
margin-top: 5px
|
||||
margin-top: 5px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
button.emphasis{
|
||||
margin-top: 10px;
|
||||
@ -47,6 +43,11 @@
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
input[type="checkbox"]{
|
||||
top: 0;
|
||||
width: inherit;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ export class LogoutPage extends React.Component {
|
||||
Session.logout()
|
||||
.then((res) => {
|
||||
cache.destroy();
|
||||
this.props.history.push('/');
|
||||
this.props.history.push('/login');
|
||||
})
|
||||
.catch((res) => {
|
||||
console.warn(res)
|
||||
|
||||
@ -41,13 +41,9 @@ func (d *Driver) Drivers() map[string]IBackend {
|
||||
}
|
||||
|
||||
type Nothing struct {}
|
||||
|
||||
func (b Nothing) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
return &Nothing{}, nil
|
||||
}
|
||||
func (b Nothing) Info() string {
|
||||
return "N/A"
|
||||
}
|
||||
func (b Nothing) Ls(path string) ([]os.FileInfo, error) {
|
||||
return nil, NewError("", 401)
|
||||
}
|
||||
@ -69,7 +65,6 @@ func (b Nothing) Touch(path string) error {
|
||||
func (b Nothing) Save(path string, file io.Reader) error {
|
||||
return NewError("", 401)
|
||||
}
|
||||
|
||||
func (b Nothing) LoginForm() Form {
|
||||
return Form{}
|
||||
}
|
||||
|
||||
@ -23,7 +23,7 @@ type Configuration struct {
|
||||
currentElement *FormElement
|
||||
cache KeyValueStore
|
||||
form []Form
|
||||
conn []map[string]interface{}
|
||||
Conn []map[string]interface{}
|
||||
}
|
||||
|
||||
type Form struct {
|
||||
@ -40,7 +40,7 @@ type FormElement struct {
|
||||
Placeholder string `json:"placeholder,omitempty"`
|
||||
Opts []string `json:"options,omitempty"`
|
||||
Target []string `json:"target,omitempty"`
|
||||
Enabled bool `json:"enabled"`
|
||||
ReadOnly bool `json:"readonly"`
|
||||
Default interface{} `json:"default"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
@ -123,7 +123,7 @@ func NewConfiguration() Configuration {
|
||||
},
|
||||
},
|
||||
},
|
||||
conn: make([]map[string]interface{}, 0),
|
||||
Conn: make([]map[string]interface{}, 0),
|
||||
}
|
||||
}
|
||||
|
||||
@ -219,7 +219,7 @@ func (this *Configuration) Load() {
|
||||
}
|
||||
|
||||
// Extract enabled backends
|
||||
this.conn = func(cFile []byte) []map[string]interface{} {
|
||||
this.Conn = func(cFile []byte) []map[string]interface{} {
|
||||
var d struct {
|
||||
Connections []map[string]interface{} `json:"connections"`
|
||||
}
|
||||
@ -286,8 +286,8 @@ func (this *Configuration) Initialise() {
|
||||
this.Get("general.host").Set(env).String()
|
||||
}
|
||||
|
||||
if len(this.conn) == 0 {
|
||||
this.conn = []map[string]interface{}{
|
||||
if len(this.Conn) == 0 {
|
||||
this.Conn = []map[string]interface{}{
|
||||
map[string]interface{}{
|
||||
"type": "webdav",
|
||||
"label": "WebDav",
|
||||
@ -332,7 +332,7 @@ func (this Configuration) Save() Configuration {
|
||||
}
|
||||
return string(a)
|
||||
})
|
||||
v, _ = sjson.Set(v, "connections", this.conn)
|
||||
v, _ = sjson.Set(v, "connections", this.Conn)
|
||||
|
||||
// deploy the config in our config.json
|
||||
file, err := os.Create(configPath)
|
||||
@ -363,7 +363,7 @@ func (this Configuration) Export() interface{} {
|
||||
AutoConnect: this.Get("general.auto_connect").Bool(),
|
||||
Name: this.Get("general.name").String(),
|
||||
RememberMe: this.Get("general.remember_me").Bool(),
|
||||
Connections: this.conn,
|
||||
Connections: this.Conn,
|
||||
EnableSearch: this.Get("features.search.enable").Bool(),
|
||||
EnableShare: this.Get("features.share.enable").Bool(),
|
||||
MimeTypes: AllMimeTypes(),
|
||||
|
||||
@ -16,7 +16,6 @@ type IBackend interface {
|
||||
Mv(from string, to string) error
|
||||
Save(path string, file io.Reader) error
|
||||
Touch(path string) error
|
||||
Info() string
|
||||
LoginForm() Form
|
||||
}
|
||||
|
||||
|
||||
@ -78,9 +78,7 @@ func AdminBackend(ctx App, res http.ResponseWriter, req *http.Request) {
|
||||
|
||||
drivers := Backend.Drivers()
|
||||
for key := range drivers {
|
||||
if obj, ok := drivers[key].(interface{ LoginForm() Form }); ok {
|
||||
backends[key] = obj.LoginForm()
|
||||
}
|
||||
backends[key] = drivers[key].LoginForm()
|
||||
}
|
||||
SendSuccessResult(res, backends)
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package ctrl
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/mickael-kerjean/mux"
|
||||
. "github.com/mickael-kerjean/nuage/server/common"
|
||||
"github.com/mickael-kerjean/nuage/server/model"
|
||||
@ -44,13 +45,14 @@ func SessionAuthenticate(ctx App, res http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
|
||||
if obj, ok := backend.(interface {
|
||||
OAuthToken(*map[string]string) error
|
||||
OAuthToken(*map[string]interface{}) error
|
||||
}); ok {
|
||||
err := obj.OAuthToken(&session)
|
||||
err := obj.OAuthToken(&ctx.Body)
|
||||
if err != nil {
|
||||
SendErrorResult(res, NewError("Can't authenticate (OAuth error)", 401))
|
||||
return
|
||||
}
|
||||
session = model.MapStringInterfaceToMapStringString(ctx.Body)
|
||||
backend, err = model.NewBackend(&ctx, session)
|
||||
if err != nil {
|
||||
SendErrorResult(res, NewError("Can't authenticate", 401))
|
||||
@ -126,7 +128,7 @@ func SessionOAuthBackend(ctx App, res http.ResponseWriter, req *http.Request) {
|
||||
}
|
||||
obj, ok := b.(interface{ OAuthURL() string })
|
||||
if ok == false {
|
||||
SendErrorResult(res, NewError("No backend authentication ("+b.Info()+")", 500))
|
||||
SendErrorResult(res, NewError(fmt.Sprintf("This backend doesn't support oauth: '%s'", a["type"]), 500))
|
||||
return
|
||||
}
|
||||
SendSuccessResult(res, obj.OAuthURL())
|
||||
|
||||
@ -14,12 +14,14 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ETAG_INDEX string
|
||||
|
||||
func StaticHandler(_path string, ctx App) http.Handler {
|
||||
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
header := res.Header()
|
||||
header.Set("Content-Type", mime.TypeByExtension(filepath.Ext(req.URL.Path)))
|
||||
header.Set("Cache-Control", "max-age=2592000")
|
||||
SecureHeader(&header)
|
||||
header.Set("X-Content-Type-Options", "nosniff")
|
||||
|
||||
if strings.HasSuffix(req.URL.Path, "/") {
|
||||
http.NotFound(res, req)
|
||||
@ -39,10 +41,23 @@ func StaticHandler(_path string, ctx App) http.Handler {
|
||||
|
||||
func DefaultHandler(_path string, ctx App) http.Handler {
|
||||
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
_path := GetAbsolutePath(_path)
|
||||
|
||||
header := res.Header()
|
||||
header.Set("Content-Type", "text/html")
|
||||
header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
SecureHeader(&header)
|
||||
header.Set("Cache-Control", "no-cache")
|
||||
header.Set("X-XSS-Protection", "1; mode=block")
|
||||
header.Set("X-Content-Type-Options", "nosniff")
|
||||
header.Set("X-Frame-Options", "DENY")
|
||||
if ETAG_INDEX == "" {
|
||||
ETAG_INDEX = hashFile(_path)
|
||||
}
|
||||
if req.Header.Get("If-None-Match") == ETAG_INDEX {
|
||||
res.WriteHeader(http.StatusNotModified)
|
||||
return
|
||||
}
|
||||
header.Set("Etag", ETAG_INDEX)
|
||||
|
||||
|
||||
// Redirect to the admin section on first boot to setup the stuff
|
||||
if req.URL.String() != URL_SETUP && Config.Get("auth.admin").String() == "" {
|
||||
@ -50,12 +65,11 @@ func DefaultHandler(_path string, ctx App) http.Handler {
|
||||
return
|
||||
}
|
||||
|
||||
p := _path
|
||||
if _, err := os.Open(path.Join(GetCurrentDir(), p+".gz")); err == nil && strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
||||
if _, err := os.Open(_path+".gz"); err == nil && strings.Contains(req.Header.Get("Accept-Encoding"), "gzip") {
|
||||
res.Header().Set("Content-Encoding", "gzip")
|
||||
p += ".gz"
|
||||
_path += ".gz"
|
||||
}
|
||||
http.ServeFile(res, req, GetAbsolutePath(p))
|
||||
http.ServeFile(res, req, _path)
|
||||
})
|
||||
}
|
||||
|
||||
@ -63,20 +77,10 @@ func AboutHandler(ctx App) http.Handler {
|
||||
return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
||||
header := res.Header()
|
||||
header.Set("Content-Type", "text/html")
|
||||
SecureHeader(&header)
|
||||
|
||||
hash := func(path string) string {
|
||||
f, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return "__"
|
||||
}
|
||||
defer f.Close()
|
||||
h := md5.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "__"
|
||||
}
|
||||
return base32.HexEncoding.EncodeToString(h.Sum(nil))[:6]
|
||||
}
|
||||
header.Set("Cache-Control", "no-cache, no-store, must-revalidate")
|
||||
header.Set("X-XSS-Protection", "1; mode=block")
|
||||
header.Set("X-Content-Type-Options", "nosniff")
|
||||
header.Set("X-Frame-Options", "DENY")
|
||||
|
||||
page := `<!DOCTYPE html>
|
||||
<html>
|
||||
@ -104,7 +108,7 @@ func AboutHandler(ctx App) http.Handler {
|
||||
App []string
|
||||
Plugins [][]string
|
||||
}{
|
||||
App: []string{"Nuage " + APP_VERSION, BUILD_NUMBER + "_" + hash(filepath.Join(GetCurrentDir(), "/nuage"))},
|
||||
App: []string{"Nuage " + APP_VERSION, BUILD_NUMBER + "_" + hashFile(filepath.Join(GetCurrentDir(), "/nuage"))},
|
||||
Plugins: func () [][]string {
|
||||
pPath := filepath.Join(GetCurrentDir(), PLUGIN_PATH)
|
||||
file, err := os.Open(pPath)
|
||||
@ -122,12 +126,12 @@ func AboutHandler(ctx App) http.Handler {
|
||||
plugins := make([][]string, 0)
|
||||
plugins = append(plugins, []string {
|
||||
"config.json",
|
||||
hash(filepath.Join(GetCurrentDir(), "/data/config/config.json")),
|
||||
hashFile(filepath.Join(GetCurrentDir(), "/data/config/config.json")),
|
||||
})
|
||||
for i:=0; i < len(files); i++ {
|
||||
plugins = append(plugins, []string{
|
||||
files[i].Name(),
|
||||
hash(pPath + "/" + files[i].Name()),
|
||||
hashFile(pPath + "/" + files[i].Name()),
|
||||
})
|
||||
}
|
||||
return plugins
|
||||
@ -136,8 +140,16 @@ func AboutHandler(ctx App) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
func SecureHeader(header *http.Header) {
|
||||
header.Set("X-XSS-Protection", "1; mode=block")
|
||||
header.Set("X-Content-Type-Options", "nosniff")
|
||||
header.Set("X-Frame-Options", "DENY")
|
||||
|
||||
func hashFile (path string) string {
|
||||
f, err := os.OpenFile(path, os.O_RDONLY, os.ModePerm)
|
||||
if err != nil {
|
||||
return "__"
|
||||
}
|
||||
defer f.Close()
|
||||
h := md5.New()
|
||||
if _, err := io.Copy(h, f); err != nil {
|
||||
return "__"
|
||||
}
|
||||
return base32.HexEncoding.EncodeToString(h.Sum(nil))[:6]
|
||||
}
|
||||
|
||||
@ -41,7 +41,7 @@ func APIHandler(fn func(App, http.ResponseWriter, *http.Request), ctx App) http.
|
||||
|
||||
go func() {
|
||||
if Config.Get("log.telemetry").Bool() {
|
||||
go telemetry(req, &resw, start, ctx.Backend.Info())
|
||||
go telemetry(req, &resw, start, ctx.Session["type"])
|
||||
}
|
||||
if Config.Get("log.enable").Bool() {
|
||||
go logger(req, &resw, start)
|
||||
@ -121,7 +121,11 @@ func ExtractSession(req *http.Request, ctx *App) (map[string]string, error) {
|
||||
return res, nil
|
||||
}
|
||||
str = cookie.Value
|
||||
str, _ = DecryptString(SECRET_KEY, str)
|
||||
str, err = DecryptString(SECRET_KEY, str)
|
||||
if err != nil {
|
||||
// This typically happen when changing the secret key
|
||||
return res, nil
|
||||
}
|
||||
err = json.Unmarshal([]byte(str), &res)
|
||||
return res, err
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ func (d Dropbox) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
backend.ClientId = Config.Get("auth.dropbox.client_id").Default("").String()
|
||||
}
|
||||
backend.Hostname = Config.Get("general.host").String()
|
||||
backend.Bearer = params["bearer"]
|
||||
backend.Bearer = params["access_token"]
|
||||
|
||||
if backend.ClientId == "" {
|
||||
return backend, NewError("Missing ClientID: Contact your admin", 502)
|
||||
@ -41,10 +41,6 @@ func (d Dropbox) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (d Dropbox) Info() string {
|
||||
return "dropbox"
|
||||
}
|
||||
|
||||
func (d Dropbox) LoginForm() Form {
|
||||
return Form{
|
||||
Elmnts: []FormElement{
|
||||
@ -54,9 +50,16 @@ func (d Dropbox) LoginForm() Form {
|
||||
Value: "dropbox",
|
||||
},
|
||||
FormElement{
|
||||
ReadOnly: true,
|
||||
Name: "oauth2",
|
||||
Type: "text",
|
||||
Value: "/api/session/auth/dropbox",
|
||||
},
|
||||
FormElement{
|
||||
ReadOnly: true,
|
||||
Name: "image",
|
||||
Type: "image",
|
||||
Value: "/assets/img/dropbox.png",
|
||||
Value: "data:image/svg+xml;utf8;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNDIuNCAzOS41IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KICA8cG9seWdvbiBmaWxsPSIjMDA3RUU1IiBwb2ludHM9IjEyLjUsMCAwLDguMSA4LjcsMTUuMSAyMS4yLDcuMyIvPgo8cG9seWdvbiBmaWxsPSIjMDA3RUU1IiBwb2ludHM9IjAsMjEuOSAxMi41LDMwLjEgMjEuMiwyMi44IDguNywxNS4xIi8+Cjxwb2x5Z29uIGZpbGw9IiMwMDdFRTUiIHBvaW50cz0iMjEuMiwyMi44IDMwLDMwLjEgNDIuNCwyMiAzMy44LDE1LjEiLz4KPHBvbHlnb24gZmlsbD0iIzAwN0VFNSIgcG9pbnRzPSI0Mi40LDguMSAzMCwwIDIxLjIsNy4zIDMzLjgsMTUuMSIvPgo8cG9seWdvbiBmaWxsPSIjMDA3RUU1IiBwb2ludHM9IjIxLjMsMjQuNCAxMi41LDMxLjcgOC44LDI5LjIgOC44LDMyIDIxLjMsMzkuNSAzMy44LDMyIDMzLjgsMjkuMiAzMCwzMS43Ii8+Cjwvc3ZnPgo=",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -70,10 +70,6 @@ func (f Ftp) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (f Ftp) Info() string {
|
||||
return "ftp"
|
||||
}
|
||||
|
||||
func (f Ftp) LoginForm() Form {
|
||||
return Form{
|
||||
Elmnts: []FormElement{
|
||||
|
||||
@ -67,10 +67,6 @@ func (g GDrive) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (g GDrive) Info() string {
|
||||
return "googledrive"
|
||||
}
|
||||
|
||||
func (g GDrive) LoginForm() Form {
|
||||
return Form{
|
||||
Elmnts: []FormElement{
|
||||
@ -80,16 +76,23 @@ func (g GDrive) LoginForm() Form {
|
||||
Value: "gdrive",
|
||||
},
|
||||
FormElement{
|
||||
ReadOnly: true,
|
||||
Name: "oauth2",
|
||||
Type: "text",
|
||||
Value: "/api/session/auth/gdrive",
|
||||
},
|
||||
FormElement{
|
||||
ReadOnly: true,
|
||||
Name: "image",
|
||||
Type: "image",
|
||||
Value: "/assets/img/google-drive.png",
|
||||
Value: "data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTM5IDEyMC40IiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIj4KICA8cGF0aCBkPSJtMjQuMiAxMjAuNC0yNC4yLTQxLjkgNDUuMy03OC41IDI0LjIgNDEuOXoiIGZpbGw9IiMwZGE5NjAiLz4KICA8cGF0aCBkPSJtNTguOSA2MC4yIDEwLjYtMTguMy0yNC4yLTQxLjl6IiBmaWxsPSIjMGRhOTYwIi8+CiAgPHBhdGggZD0ibTI0LjIgMTIwLjQgMjQuMi00MS45aDkwLjZsLTI0LjIgNDEuOXoiIGZpbGw9IiMyZDZmZGQiLz4KICA8cGF0aCBkPSJtNjkuNSA3OC41aC0yMS4xbDEwLjUtMTguMy0zNC43IDYwLjJ6IiBmaWxsPSIjMmQ2ZmRkIi8+ICAKICA8cGF0aCBkPSJtMTM5IDc4LjVoLTQ4LjRsLTQ1LjMtNzguNWg0OC40eiIgZmlsbD0iI2ZmZDI0ZCIvPgogIDxwYXRoIGQ9Im05MC42IDc4LjVoNDguNGwtNTguOS0xOC4zeiIgZmlsbD0iI2ZmZDI0ZCIvPgo8L3N2Zz4K",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (g GDrive) OAuthURL() string {
|
||||
return g.Config.AuthCodeURL("googledrive", oauth2.AccessTypeOnline)
|
||||
return g.Config.AuthCodeURL("gdrive", oauth2.AccessTypeOnline)
|
||||
}
|
||||
|
||||
func (g GDrive) OAuthToken(ctx *map[string]interface{}) error {
|
||||
|
||||
@ -108,10 +108,6 @@ func (git Git) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
return g, nil
|
||||
}
|
||||
|
||||
func (g Git) Info() string {
|
||||
return "git"
|
||||
}
|
||||
|
||||
func (g Git) LoginForm() Form {
|
||||
return Form{
|
||||
Elmnts: []FormElement{
|
||||
@ -132,7 +128,7 @@ func (g Git) LoginForm() Form {
|
||||
},
|
||||
FormElement{
|
||||
Name: "password",
|
||||
Type: "password",
|
||||
Type: "long_password",
|
||||
Placeholder: "Password",
|
||||
},
|
||||
FormElement{
|
||||
|
||||
@ -53,10 +53,6 @@ func (s S3Backend) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (s S3Backend) Info() string {
|
||||
return "s3"
|
||||
}
|
||||
|
||||
func (s S3Backend) LoginForm() Form {
|
||||
return Form{
|
||||
Elmnts: []FormElement{
|
||||
|
||||
@ -41,7 +41,6 @@ func (s Sftp) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
params["password"],
|
||||
params["passphrase"],
|
||||
}
|
||||
|
||||
if p.port == "" {
|
||||
p.port = "22"
|
||||
}
|
||||
@ -91,10 +90,6 @@ func (s Sftp) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
return &s, nil
|
||||
}
|
||||
|
||||
func (b Sftp) Info() string {
|
||||
return "sftp"
|
||||
}
|
||||
|
||||
func (b Sftp) LoginForm() Form {
|
||||
return Form{
|
||||
Elmnts: []FormElement{
|
||||
@ -115,7 +110,7 @@ func (b Sftp) LoginForm() Form {
|
||||
},
|
||||
FormElement{
|
||||
Name: "password",
|
||||
Type: "password",
|
||||
Type: "long_password",
|
||||
Placeholder: "Password",
|
||||
},
|
||||
FormElement{
|
||||
|
||||
@ -41,10 +41,6 @@ func (w WebDav) Init(params map[string]string, app *App) (IBackend, error) {
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (w WebDav) Info() string {
|
||||
return "webdav"
|
||||
}
|
||||
|
||||
func (w WebDav) LoginForm() Form {
|
||||
return Form{
|
||||
Elmnts: []FormElement{
|
||||
|
||||
@ -9,32 +9,39 @@ import (
|
||||
|
||||
func NewBackend(ctx *App, conn map[string]string) (IBackend, error) {
|
||||
isAllowed := func() bool {
|
||||
// by default, a hacker could use filestash to establish connections outside of what's
|
||||
// define in the config file. We need to prevent this
|
||||
possibilities := make([]map[string]interface{}, 0)
|
||||
for i:=0; i< len(Config.Conn); i++ {
|
||||
d := Config.Conn[i]
|
||||
if d["type"] != conn["type"] {
|
||||
continue
|
||||
}
|
||||
if val, ok := d["hostname"]; ok == true {
|
||||
if val != conn["hostname"] {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if val, ok := d["path"]; ok == true {
|
||||
if val != conn["path"] {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if val, ok := d["url"]; ok == true {
|
||||
if val != conn["url"] {
|
||||
continue
|
||||
}
|
||||
}
|
||||
possibilities = append(possibilities, Config.Conn[i])
|
||||
}
|
||||
if len(possibilities) > 0 {
|
||||
return true
|
||||
// ret := false
|
||||
// var conns [] struct {
|
||||
// Type string `json:"type"`
|
||||
// Hostname string `json:"hostname"`
|
||||
// Path string `json:"path"`
|
||||
// }
|
||||
// Config.Get("connections").Interface()
|
||||
// Config.Get("connections").Scan(&conns)
|
||||
// for i := range conns {
|
||||
// if conns[i].Type == conn["type"] {
|
||||
// if conns[i].Hostname != "" && conns[i].Hostname != conn["hostname"] {
|
||||
// continue
|
||||
// } else if conns[i].Path != "" && conns[i].Path != conn["path"] {
|
||||
// continue
|
||||
// } else {
|
||||
// ret = true
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// return ret
|
||||
}()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
if isAllowed == false {
|
||||
return Backend.Get(BACKEND_NIL).Init(conn, ctx)
|
||||
if isAllowed() == false {
|
||||
return Backend.Get(BACKEND_NIL), ErrNotAllowed
|
||||
}
|
||||
return Backend.Get(conn["type"]).Init(conn, ctx)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user